在Swift中从nib加载UIView

Bag*_*yer 140 xib uiview swift

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

-(id)init{

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil];
    return [subviewArray objectAtIndex:0];

}
Run Code Online (Sandbox Code Playgroud)

Swift中的等效代码是什么?

Rob*_*son 287

我的贡献:

Swift 3/Swift 4

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

然后像这样称呼它:

let myCustomView: CustomView = UIView.fromNib()
Run Code Online (Sandbox Code Playgroud)

..甚至:

let myCustomView: CustomView = .fromNib()
Run Code Online (Sandbox Code Playgroud)

  • 到目前为止最佳答案. (17认同)
  • **这里最好**回答.干净简单 (5认同)
  • @YuchenZhong - 我更喜欢[0]而不是.first,因为它会返回一个可选项.如果你强行打开它,它就不会更安全.......这引出了一个问题:为什么不返回一个可选的上述解决方案?答:你可以.没有错.但是......如果它返回nil,则xib/class的名称不匹配.这是一个开发人员的错误,应该立即捕获,永远不会生产.在这里,我希望让应用程序崩溃,让它处于某种奇怪的状态.只需2美分/偏好. (3认同)
  • 在.xib中有自定义视图的情况下,此解决方案对我不起作用.我建议修复这部分:返回Bundle.main.loadNibNamed(String(describe:self),owner:nil,options:nil)![0] as!Ť (2认同)

GK1*_*100 163

原始方案

  1. 我创建了一个名为SomeView的XIB和一个类(为了方便和可读性,使用了相同的名称).我基于UIView.
  2. 在XIB中,我将"文件所有者"类更改为SomeView(在身份检查器中).
  3. 我在SomeView.swift中创建了一个UIView插座,将它链接到XIB文件中的顶级视图(为方便起见,将其命名为"view").然后,我根据需要将其他插座添加到XIB文件中的其他控件.
  4. 在SomeView.swift中,我将XIB加载到"init with code"初始化器中.没有必要为"自我"分配任何东西.加载XIB后,所有插座都会连接,包括顶层视图.唯一缺少的是将顶视图添加到视图层次结构中:

.

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
   }
   ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,这样我获得了一个从nib加载自身的类.然后,只要UIView可以在项目中使用(在界面生成器中或以编程方式),我就可以将SomeView用作类.

更新 - 使用Swift 3语法

在以下扩展中加载xib是作为实例方法编写的,然后可以像上面的初始化程序一样使用它:

extension UIView {

    @discardableResult   // 1
    func fromNib<T : UIView>() -> T? {   // 2
        guard let contentView = Bundle(for: type(of: self)).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
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 使用可丢弃的返回值,因为当所有插座已连接时,返回的视图通常对调用者不感兴趣.
  2. 这是一个返回UIView类型的可选对象的通用方法.如果无法加载视图,则返回nil.
  3. 尝试加载与当前类实例同名的XIB文件.如果失败,则返回nil.
  4. 将顶级视图添加到视图层次结构.
  5. 该行假设我们使用约束来布局视图.
  6. 此方法添加了top,bottom,leading和trailing约束 - 将视图附加到所有方面的"self"(有关详细信息,请参阅: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 ...
}
Run Code Online (Sandbox Code Playgroud)
  1. SomeClass是一个UIView子类,它从SomeClass.xib文件加载其内容."final"关键字是可选的.
  2. 用于在故事板中使用视图的初始化程序(请记住使用SomeClass作为故事板视图的自定义类).
  3. 用于以编程方式创建视图的初始化程序(即:"let myView = SomeView()").
  4. 使用全零帧,因为此视图使用自动布局布局.请注意,"init(frame:CGRect){..}"方法不是独立创建的,因为自动布局仅在我们的项目中使用.
  5. &6.使用扩展名加载xib文件.

信用:在此解决方案中使用通用扩展程序的灵感来自Robert的答案.

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

  • UIView没有属性视图,因此调用self.view会导致错误 (12认同)
  • 从`init(coder aDecoder:NSCoder)中调用`fromNib()`会创建一个无限循环,因为在`fromNib()`方法中加载Nib会调用:`init(coder aDecoder:NSCoder)` (9认同)
  • 将类名设置为`File's Owner`就在现场......谢谢! (6认同)
  • @NastyaGorban self.view在这种情况下实际上指的是出口属性(名为"view"),GK100从.xib中的顶级视图链接到SomeView.swift.不添加该出口会给你错误,因为没有"视图"就像你说的那样在NSView类中的属性. (4认同)
  • 我在加载nib(loadNibNamed)时遇到崩溃.使用Xcode 6.3和Swift (3认同)
  • 不要设置视图类. (2认同)
  • 我错过了layoutAttachAll的描述,我认为这是一个重命名的方法,并且在这个问题中找到了一个实现,如果有人需要它:/sf/ask/2929632471/ (2认同)
  • 有没有人找到@MatthewCawley 提到的循环? (2认同)
  • @CSjunkie - 解决方法是在 `init?(coder aDecoder: NSCoder)` 方法中不调用 `fromNib()`。:) (2认同)

Log*_*gan 76

这是一个使用泛型UIView从笔尖加载a的扩展

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我更喜欢这个,因为它不需要在笔尖中进行任何额外的设置.它基于通用命名约定,因此如果您的类是CustomView,并且它匹配名为:的nib CustomView,您可以这样做:

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

如果您因任何原因需要具体了解nib名称,请传递一个字符串arg:

let myCustomView = MyCustomView.fromNib("non-conventional-name")
Run Code Online (Sandbox Code Playgroud)

已知的问题

使用私有视图类似乎会导致问题.这似乎是一个系统问题.

  • 我无法让它在 XCode 7.1 beta 3 下工作 - 不确定它是否是 beta 的东西,但基本上我已经尝试了各种方法来直接从 Swift 中的笔尖创建自定义视图,我总是得到相同的结果:它正在创建的类与插座不兼容。不确定这是不是我做错了,但我的课程非常简单,文件所有者是正确的。我曾经在 Objective-C 下一直这样做。 (2认同)
  • @Logan 它与您的代码并没有真正相关,但是 imo 自定义视图应该支持从 Storyboard/XIB 加载。我的评论只是给那些想要创建此类视图的人的通知 (2认同)
  • 重要的是要指出,尝试使用文件所有者来连接出口(正如我们在好日子里所做的那样)将导致崩溃.在IB中,文件所有者必须为空/空,并且应将连接符连接到视图. (2认同)

Jas*_*min 17

尝试以下代码.

var uiview :UIView?

self.uiview = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)[0] as? UIView
Run Code Online (Sandbox Code Playgroud)

编辑:

import UIKit

class TestObject: NSObject {

     var uiview:UIView?

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


}
Run Code Online (Sandbox Code Playgroud)


Amr*_*fie 10

我通过以下代码用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)
    }
}
Run Code Online (Sandbox Code Playgroud)

不要忘记将XIB 视图插座连接到swift中定义的视图插座.您还可以将First Responder设置为您的自定义类名称,以开始连接任何其他插座.

希望这可以帮助!


kar*_*agu 9

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



    }


}
Run Code Online (Sandbox Code Playgroud)

以编程方式访问customview

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);
Run Code Online (Sandbox Code Playgroud)

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


Bro*_*son 8

Swift 4协议扩展

public protocol NibInstantiatable {

    static func nibName() -> String

}

extension NibInstantiatable {

    static func nibName() -> String {
        return String(describing: self)
    }

}

extension NibInstantiatable where Self: UIView {

    static func fromNib() -> Self {

        let bundle = Bundle(for: self)
        let nib = bundle.loadNibNamed(nibName(), owner: self, options: nil)

        return nib!.first as! Self

    }

}
Run Code Online (Sandbox Code Playgroud)

采用

class MyView: UIView, NibInstantiatable {

}
Run Code Online (Sandbox Code Playgroud)

此实现假定Nib与UIView类具有相同的名称。例如 MyView.xib。您可以通过在MyView中实现nibName()返回与默认协议扩展实现不同的名称来修改此行为。

在xib中,文件所有者是MyView,根视图类是MyView。

用法

let view = MyView.fromNib()
Run Code Online (Sandbox Code Playgroud)

  • 到目前为止,这是最优雅,最直接的解决方案,我不知道为什么它不是公认的答案! (2认同)
  • @horseshoe7 因为它是在问题提出四年后写的。 (2认同)

con*_*ile 7

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

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

    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
      }
    
    
      ...
    }
    
    Run Code Online (Sandbox Code Playgroud)


Chi*_*buZ 7

如果项目中有很多自定义视图,则可以创建类 UIViewFromNib

斯威夫特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)
    }
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特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)
    }
}
Run Code Online (Sandbox Code Playgroud)

并且在每个继承的类中UIViewFromNib,nibName如果.xib文件具有不同的名称,您也可以覆盖属性:

class MyCustomClass: UIViewFromNib {

}
Run Code Online (Sandbox Code Playgroud)


Gen*_*sis 7

建立在上述解决方案之上.

这将适用于所有项目包,并且在调用fromNib()时不需要泛型.

斯威夫特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)
    }
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特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)
    }
}
Run Code Online (Sandbox Code Playgroud)

可以像这样使用:

let someView = SomeView.fromNib()
Run Code Online (Sandbox Code Playgroud)

或者像这样:

let someView = SomeView.fromNib("SomeOtherNibFileName")
Run Code Online (Sandbox Code Playgroud)


Top*_*ter 7

更新为Swift 5

某处定义如下:

extension UIView {
    public class func fromNib<T: UIView>() -> T {
        let name = String(describing: Self.self);
        guard let nib = Bundle(for: Self.self).loadNibNamed(
                name, owner: nil, options: nil)
        else {
            fatalError("Missing nib-file named: \(name)")
        }
        return nib.first as! T
    }
}
Run Code Online (Sandbox Code Playgroud)

并像上面一样使用:

let view: MyCustomView = .fromNib();
Run Code Online (Sandbox Code Playgroud)

它将在与 相同的包中搜索MyCustomView,然后加载MyCustomView.nib文件(如果文件存在,并且添加到项目中)。


Ali*_*RAL 6

斯威夫特 4

不要忘记写“.first as?CustomView”。

if let customView = Bundle.main.loadNibNamed("myXib", owner: self, options: nil)?.first as? CustomView {    
    self.view.addSubview(customView)
    }
Run Code Online (Sandbox Code Playgroud)

如果你想在任何地方使用

最佳解决方案是Robert Gummesson的回答。

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

然后像这样调用它:

let myCustomView: CustomView = UIView.fromNib()
Run Code Online (Sandbox Code Playgroud)


BaS*_*Sha 5

let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]
Run Code Online (Sandbox Code Playgroud)


tot*_*tiG 5

使用Swift执行此操作的一个好方法是使用枚举.

enum Views: String {
    case view1 = "View1" // Change View1 to be the name of your nib
    case view2 = "View2" // Change View2 to be the name of another nib

    func getView() -> UIView? {
        return NSBundle.mainBundle().loadNibNamed(self.rawValue, owner: nil, options: nil).first as? UIView
    }
}
Run Code Online (Sandbox Code Playgroud)

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

let view = Views.view1.getView()
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果使用空的nib文件或UIView根节点为no的nib文件执行此操作,则将崩溃,因为您没有健全性检查数组大小或位置0的元素。 (2认同)

Moo*_*ose 5

我只是这样做:

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

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

我曾经将视图唤醒到视图初始化方法中,或者按照上面建议的答案创建通用方法(顺便说一下,这很聪明),但我不再这样做了,因为我注意到用例通常是不同的,并且要涵盖在所有情况下,通用方法都变得与使用 UINib.instantiate 方法一样复杂。

我更喜欢使用工厂对象,通常是使用视图的 ViewController,或者如果需要在多个地方使用视图,则使用专用工厂对象或视图扩展。

在此示例中,ViewController 从 nib 加载视图。可以更改 nib 文件以对同一视图类使用不同的布局。(这不是很好的代码,它只是说明了这个想法)

class MyViewController {
    // Use "MyView-Compact" for compact version
    var myViewNibFileName = "MyView-Standard"

    lazy var myView: MyView = {
        // Be sure the Nib is correct, or it will crash
        // We don't want to continue with a wrong view anyway, so ! is ok
        UINib.init(nibName: myViewNibFileName, bundle: nil).instantiate(withOwner: self)[0] as! MyView
    }()
}
Run Code Online (Sandbox Code Playgroud)


Ess*_*med 5

Swift 5 - 干净且易于使用的扩展

[从生产项目复制粘贴]

//
//  Refactored by Essam Mohamed Fahmi.
//

import UIKit

extension UIView
{
   static var nib: UINib
   {
      return UINib(nibName: "\(self)", bundle: nil)
   }

   static func instantiateFromNib() -> Self?
   {
      return nib.instantiate() as? Self
   }
}

extension UINib
{
   func instantiate() -> Any?
   {
      return instantiate(withOwner: nil, options: nil).first
   }
}
Run Code Online (Sandbox Code Playgroud)

用法

let myCustomView: CustomView = .instantiateFromNib()
Run Code Online (Sandbox Code Playgroud)