在Swift中从nib加载一个UIView

这里是我用来加载我的自定义UIView一个笔尖的Objective-C代码:

 -(id)init{ NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil]; return [subviewArray objectAtIndex:0]; } 

什么是Swift中的等效代码?

Orignal解决scheme

  1. 我创build了一个XIB和一个名为SomeView的类(为了方便和可读性使用了相同的名称)。 我基于一个UIView。
  2. 在XIB中,我将“文件所有者”类更改为SomeView(在身份检查器中)。
  3. 我在SomeView.swift中创build了一个UIViewsockets,将它链接到XIB文件中的顶层视图(为了方便,命名为“视图”)。 然后根据需要将其他插口添加到XIB文件中的其他控件。
  4. 在SomeView.swift中,我在“init with code”初始值设定项中加载了XIB。 没有必要给“自我”分配任何东西。 一旦加载了XIB,所有sockets都连接上了,包括顶层视图。 唯一缺less的是将顶视图添加到视图层次结构中:

 class SomeView: UIView { required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } ... } 

请注意,这样我得到一个从nib加载自己的类。 然后我可以使用SomeView作为一个类,只要UIView可以在项目中使用(在界面构build器或以编程方式)。

更新 – 使用Swift 3语法

在下面的扩展中加载一个xib被写为一个实例方法,然后可以被上面的初始化程序使用:

 extension UIView { @discardableResult // 1 func fromNib<T : UIView>() -> T? { // 2 guard let contentView = Bundle.main.loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else { // 3 // xib not loaded, or its top view is of the wrong type return nil } self.addSubview(contentView) // 4 contentView.translatesAutoresizingMaskIntoConstraints = false // 5 contentView.layoutAttachAll(to: self) // 6 return contentView // 7 } } 
  1. 使用可丢弃的返回值,因为当所有的sockets已经连接时,返回的视图大部分是不感兴趣的。
  2. 这是返回UIViewtypes的可选对象的generics方法。 如果无法加载视图,则返回nil。
  3. 尝试加载与当前类实例具有相同名称的XIB文件。 如果失败,则返回nil。
  4. 将顶级视图添加到视图层次结构中。
  5. 这一行假定我们使用约束来布局视图。
  6. 这种方法增加了顶部,底部,前后约束 – 将视图附加到所有方面的“自我”(详见https://stackoverflow.com/a/46279424/2274829
  7. 返回顶层视图

而调用方法可能如下所示:

 final class SomeView: UIView { // 1. required init(coder aDecoder: NSCoder) { // 2 - storyboard initializer super.init(coder: aDecoder) fromNib() // 5. } init() { // 3 - programmatic initializer super.init(frame: CGRect.zero) // 4. fromNib() // 6. } // other methods ... } 
  1. SomeClass是从SomeClass.xib文件加载其内容的UIView子类。 “最终”关键字是可选的。
  2. 视图在故事板中使用时的初始化程序(请记住使用SomeClass作为故事板视图的自定义类)。
  3. 视图以编程方式创build的初始化工具(即:“let myView = SomeView()”)。
  4. 由于此视图使用自动布局布置,因此使用全零框架。 请注意,“init(frame:CGRect){..}”方法不是独立创build的,因为自动布局在我们的项目中是专门使用的。
  5. &6.使用扩展名加载xib文件。

信用:在这个解决scheme中使用一个通用的扩展,受到了罗伯特的答案的启发。

编辑将“视图”更改为“contentView”以避免混淆。 还将数组下标改为“.first”。

我的贡献:

Swift 3 / Swift 4

 extension UIView { class func fromNib<T: UIView>() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T } } 

然后像这样调用它:

 let myCustomView: CustomView = UIView.fromNib() 

..甚至:

 let myCustomView: CustomView = .fromNib() 

这是一个使用generics从nib加载UIView的扩展

 public extension UIView { public class func fromNib(nibNameOrNil: String? = nil) -> Self { return fromNib(nibNameOrNil, type: self) } public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T { let v: T? = fromNib(nibNameOrNil, type: T.self) return v! } public class func fromNib<T : UIView>(nibNameOrNil: String? = nil, type: T.Type) -> T? { var view: T? let name: String if let nibName = nibNameOrNil { name = nibName } else { // Most nibs are demangled by practice, if not, just declare string explicitly name = nibName } let nibViews = NSBundle.mainBundle().loadNibNamed(name, owner: nil, options: nil) for v in nibViews { if let tog = v as? T { view = tog } } return view } public class var nibName: String { let name = "\(self)".componentsSeparatedByString(".").first ?? "" return name } public class var nib: UINib? { if let _ = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") { return UINib(nibName: nibName, bundle: nil) } else { return nil } } } 

我更喜欢这个,因为它不需要任何额外的设置在笔尖。 它基于一般的命名约定,所以如果你的类是CustomView ,并且匹配名为CustomView的nib,你可以这样做:

 let myCustomView = CustomView.fromNib() // or if you're unsure whether or not the nib exists let myCustomView: CustomView? = CustomView.fromNib() 

如果因为某种原因需要详细说明nib名称,请传递一个stringarg:

 let myCustomView = MyCustomView.fromNib("non-conventional-name") 

已知的问题

在私有视图类中使用它似乎会导致问题。 这似乎是一个系统广泛的问题。

尝试下面的代码。

 var uiview :UIView? self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView 

编辑:

 import UIKit class TestObject: NSObject { var uiview:UIView? init() { super.init() self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView } } 

我用Swift通过以下代码实现了这一点:

 class Dialog: UIView { @IBOutlet var view:UIView! override init(frame: CGRect) { super.init(frame: frame) self.frame = UIScreen.mainScreen().bounds NSBundle.mainBundle().loadNibNamed("Dialog", owner: self, options: nil) self.view.frame = UIScreen.mainScreen().bounds self.addSubview(self.view) } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } } 

不要忘记连接你的XIB 视图sockets来查看 swift中定义的sockets。 您还可以将First Responder设置为您的自定义class级名称,以开始连接任何其他网点。

希望这可以帮助!

testing了Xcode 7 beta 4,Swift 2.0和iOS9 SDK。 以下代码将xib分配给uiview。 您可以在故事板中使用此自定义xib视图,也可以访问IBOutlet对象。

 import UIKit @IBDesignable class SimpleCustomView:UIView { var view:UIView!; @IBOutlet weak var lblTitle: UILabel! @IBInspectable var lblTitleText : String? { get{ return lblTitle.text; } set(lblTitleText) { lblTitle.text = lblTitleText!; } } override init(frame: CGRect) { super.init(frame: frame) loadViewFromNib () } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadViewFromNib () } func loadViewFromNib() { let bundle = NSBundle(forClass: self.dynamicType) let nib = UINib(nibName: "SimpleCustomView", bundle: bundle) let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView view.frame = bounds view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] self.addSubview(view); } } 

以编程方式访问customview

 self.customView = SimpleCustomView(frame: CGRectMake(100, 100, 200, 200)) self.view.addSubview(self.customView!); 

源代码 – https://github.com/karthikprabhuA/CustomXIBSwift

如果你的项目中有很多的自定义视图,你可以创build像UIViewFromNib这样的UIViewFromNib

Swift 2.3

 class UIViewFromNib: UIView { var contentView: UIView! var nibName: String { return String(self.dynamicType) } //MARK: override init(frame: CGRect) { super.init(frame: frame) loadViewFromNib() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadViewFromNib() } //MARK: private func loadViewFromNib() { contentView = NSBundle.mainBundle().loadNibNamed(nibName, owner: self, options: nil)[0] as! UIView contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] contentView.frame = bounds addSubview(contentView) } } 

Swift 3

 class UIViewFromNib: UIView { var contentView: UIView! var nibName: String { return String(describing: type(of: self)) } //MARK: override init(frame: CGRect) { super.init(frame: frame) loadViewFromNib() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadViewFromNib() } //MARK: func loadViewFromNib() { contentView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?[0] as! UIView contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] contentView.frame = bounds addSubview(contentView) } } 

而且在每个类只是从UIViewFromNibinheritance,如果nibName文件具有不同的名称,也可以重写nibName属性:

 class MyCustomClass: UIViewFromNib { } 

用Swift做这件事的一个好方法是使用枚举。

 enum Views: String { case view1 = "View1" //Change View1 to be the name of your nib case view2 = "View2" func getView() -> UIView { return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil)[0] as! UIView } } 

然后在你的代码中,你可以简单地使用:

 let view = Views.view1.getView() 

基于上述解决scheme。

这将跨所有项目捆绑包工作,并从Nib()调用时不需要generics。

Swift 2

 extension UIView { public class func fromNib() -> Self { return fromNib(nil) } public class func fromNib(nibName: String?) -> Self { func fromNibHelper<T where T : UIView>(nibName: String?) -> T { let bundle = NSBundle(forClass: T.self) let name = nibName ?? String(T.self) return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T() } return fromNibHelper(nibName) } } 

Swift 3

 extension UIView { public class func fromNib() -> Self { return fromNib(nibName: nil) } public class func fromNib(nibName: String?) -> Self { func fromNibHelper<T>(nibName: String?) -> T where T : UIView { let bundle = Bundle(for: T.self) let name = nibName ?? String(describing: T.self) return bundle.loadNibNamed(name, owner: nil, options: nil)?.first as? T ?? T() } return fromNibHelper(nibName: nibName) } } 

可以这样使用:

 let someView = SomeView.fromNib() 

或者像这样:

 let someView = SomeView.fromNib("SomeOtherNibFileName") 

洛根的答案Swift 3版本

 extension UIView { public class func fromNib(nibName: String? = nil) -> Self { return fromNib(nibName: nibName, type: self) } public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T { return fromNib(nibName: nibName, type: T.self)! } public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? { var view: T? let name: String if let nibName = nibName { name = nibName } else { name = self.nibName } if let nibViews = Bundle.main.loadNibNamed(name, owner: nil, options: nil) { for nibView in nibViews { if let tog = nibView as? T { view = tog } } } return view } public class var nibName: String { return "\(self)".components(separatedBy: ".").first ?? "" } public class var nib: UINib? { if let _ = Bundle.main.path(forResource: nibName, ofType: "nib") { return UINib(nibName: nibName, bundle: nil) } else { return nil } } } 
 let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil) return subviewArray[0] 

我更喜欢这个解决scheme(基于@ GK100的答案):

  1. 我创build了一个XIB和一个名为SomeView的类(为了方便和可读性使用了相同的名称)。 我基于一个UIView。
  2. 在XIB中,我将“文件所有者”类更改为SomeView(在身份检查器中)。
  3. 我在SomeView.swift中创build了一个UIViewsockets,将它链接到XIB文件中的顶层视图(为了方便,命名为“视图”)。 然后根据需要将其他插口添加到XIB文件中的其他控件。
  4. 在SomeView.swift中,我在initinit:frame: CGRect初始化程序中加载了XIB。 没有必要给“自我”分配任何东西。 一旦加载了XIB,所有sockets都连接上了,包括顶层视图。 唯一缺less的是将顶视图添加到视图层次结构中:

     class SomeView: UIView { override init(frame: CGRect) { super.init(frame: frame) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("SomeObject", owner: self, options: nil) self.addSubview(self.view); // adding the top level view to the view hierarchy } ... } 

你所要做的就是在你的UIView类中调用init方法。

这样做:

 class className: UIView { @IBOutlet var view: UIView! override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! } func setup() { UINib(nibName: "nib", bundle: nil).instantiateWithOwner(self, options: nil) addSubview(view) view.frame = self.bounds } } 

现在,如果你想添加这个视图作为视图控制器的子视图,在视图controller.swift文件中这样做:

 self.view.addSubview(className()) 

类似于上面的一些答案,但更一致的Swift3 UIView扩展:

 extension UIView { class func fromNib<A: UIView> (nibName name: String, bundle: Bundle? = nil) -> A? { let bundle = bundle ?? Bundle.main let nibViews = bundle.loadNibNamed(name, owner: self, options: nil) return nibViews?.first as? A } class func fromNib<T: UIView>() -> T? { return fromNib(nibName: String(describing: T.self), bundle: nil) } } 

这样可以方便地从一个自定义的nib中加载这个类,也可以从其他的nibs / bundles中加载这个类。

我只是这样做:

 if let myView = UINib.init(nibName: "MyView", bundle: nil).instantiate(withOwner: self)[0] as? MyView { // Do something with myView } 

本示例使用主包中的笔尖“MyView.xib”中的第一个视图。 但是,您可以改变索引,笔尖名称或捆绑包(默认为主)。

我曾经在视图init方法中唤醒视图,或者像在上面的解决scheme中那样创buildgenerics方法(这很聪明),但是我不再这样做了。

这样我可以使用不同的布局或特性,同时保持相同的视图逻辑和代码。

我发现让一个工厂对象(通常是使用视图的viewController)根据需要创build对象更容易。 有时你需要一个所有者(通常当创build的视图有一个连接到创build者的sockets),有时候不是。

这可能是苹果在其UIView类中没有包含initFromNib方法的原因。

以一个底层的例子,你不知道你是如何出生的。 你就是天生的 同样的意见;)

根据洛根的答案更强大的版本

 extension UIView { public class func fromNib(nibName: String? = nil) -> Self { return fromNib(nibName: nibName, type: self) } public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T { return fromNib(nibName: nibName, type: T.self)! } public class func fromNib<T: UIView>(nibName: String? = nil, type: T.Type) -> T? { var view: T? let name: String if let nibName = nibName { name = nibName } else { name = self.nibName } if let nibViews = nibBundle.loadNibNamed(name, owner: nil, options: nil) { if nibViews.indices.contains(nibIndex), let tog = nibViews[nibIndex] as? T { view = tog } } return view } public class var nibName: String { return "\(self)".components(separatedBy: ".").first ?? "" } public class var nibIndex: Int { return 0 } public class var nibBundle: Bundle { return Bundle.main } } 

你可以使用像

 class BaseView: UIView { override class var nibName: String { return "BaseView" } weak var delegate: StandardStateViewDelegate? } class ChildView: BaseView { override class var nibIndex: Int { return 1 } }