在Swift中从笔尖加载UIView


148

这是我的Objective-C代码,我正在使用该代码为我的自定义加载笔尖 UIView

-(id)init{

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

}

Swift中的等效代码是什么?

Answers:


172

原始解决方案

  1. 我创建了一个XIB和一个名为SomeView的类(为方便起见,使用了相同的名称)。我都基于UIView。
  2. 在XIB中,我将“文件所有者”类更改为SomeView(在身份检查器中)。
  3. 我在SomeView.swift中创建了一个UIView出口,并将其链接到XIB文件中的顶级视图(为方便起见,将其命名为“视图”)。然后,根据需要将其他插座添加到XIB文件中的其他控件。
  4. 在SomeView.swift中,我将XIB加载到“使用代码初始化”初始化程序中。无需为“自己”分配任何内容。加载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
   }
   ...
}

请注意,通过这种方式,我得到了一个从笔尖加载自身的类。然后,只要可以在项目中使用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
    }
}
  1. 使用可丢弃的返回值,因为当所有插座都已连接时,调用者对返回的视图几乎没有兴趣。
  2. 这是一个通用方法,它返回类型为UIView的可选对象。如果无法加载视图,则返回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是一个UIView子类,可从SomeClass.xib文件加载其内容。“最终”关键字是可选的。
  2. 用于在情节提要中使用视图的初始化程序(请记住,将SomeClass用作情节提要视图的自定义类)。
  3. 用于以编程方式创建视图时的初始化程序(即:“ let myView = SomeView()”)。
  4. 使用全零帧,因为此视图是使用自动布局进行布局的。请注意,“ init(frame:CGRect){..}”方法不是独立创建的,因为自动布局仅在我们的项目中使用。
  5. &6.使用扩展名加载xib文件。

信用:在此解决方案中使用通用扩展名的灵感来自以下罗伯特的回答。

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


7
设置班级名称为File's Owner现场...谢谢!
Aviel Gross

13
UIView没有属性视图,因此调用self.view会导致错误
Nastya Gorban 2015年

4
@NastyaGorban self.view实际上在这种情况下是指GK100从.xib的顶级视图链接到SomeView.swift的插座属性(名为“视图”)。不添加该插座将给您错误,因为没有“视图” “属性,就像您说的那样。
Ausiàs,2015年

3
加载nib(loadNibNamed)时出现崩溃。使用Xcode 6.3和Swift
karthikPrabhu Alagu

11
fromNib()从内部调用init(coder aDecoder: NSCoder)会导致无限循环,因为在fromNib()方法中加载Nib会导致以下调用:init(coder aDecoder: NSCoder)
Matthew Cawley

333

我的贡献:

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

然后这样称呼它:

let myCustomView: CustomView = UIView.fromNib()

..甚至:

let myCustomView: CustomView = .fromNib()

20
迄今为止最好的答案。
CodyMace

7
最好的答案在这里。干净和简单
Marquavious Draggon

3
@YuchenZhong-我偏爱[0]而不是.first,因为它会返回一个可选值。如果您强行打开包装,那将不会更安全。...这引出了一个问题:为什么不返回上述某些解决方案的可选内容?答:可以。没有错。但是...如果它将返回nil,则xib / class的名称将不匹配。这是开发人员的错误,应立即发现并永远不要投入生产。在这里,我宁愿使应用程序崩溃而不是使其处于某种怪异状态。仅我的2美分/偏好。
罗伯特·古默森,

1
@allenlinli-该方法是UIView的静态扩展,如自定义视图那样。之所以有效,是因为编译器使用显式类型注释来推断类型。因为CustomView是UIView的子类并且类型已经被推断,所以我们不需要再次推断它,因此UIView可以省略,如我的第二个示例所示。话虽这么说,您显然也可以按照拨打电话的方式拨打电话。
罗伯特·古默森,

3
在.xib中存在自定义视图的情况下,该解决方案对我不起作用。我建议将这一部分修复为:返回Bundle.main.loadNibNamed(String(describing:self),owner:nil,options:nil)![0]为!T
Nadzeya

79

现在能够-> Self快速返回有助于简化此过程。最后一次确认是在Swift 5上。

extension UIView {
    class func fromNib(named: String? = nil) -> Self {
        let name = named ?? "\(Self.self)"
        guard
            let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil)
            else { fatalError("missing expected nib named: \(name)") }
        guard
            /// we're using `first` here because compact map chokes compiler on
            /// optimized release, so you can't use two views in one nib if you wanted to
            /// and are now looking at this
            let view = nib.first as? Self
            else { fatalError("view of type \(Self.self) not found in \(nib)") }
        return view
    }
}

如果.xib文件和子类共享相同的名称,则可以使用:

let view = CustomView.fromNib()

如果您有一个自定义名称,请使用:

let view = CustomView.fromNib(named: "special-case")

注意:

如果您收到错误消息“找不到类型为YourType的视图。”,则说明您尚未在 .xib文件中

.xib文件中选择您的视图,然后按,cmd + opt + 4然后在class输入中输入您的课程


1
我不能让它在XCode 7.1 beta 3下工作-不确定是否是beta版本,但基本上我已经尝试了各种方法直接从Swift中的笔尖创建自定义视图,并且我总是得到相同的结果:正在创建的类与插座不兼容KVC。不知道这是我做错了什么,但我的课程很简单并且文件的所有者正确。我过去经常在Objective-C下这样做。
梯队2015年

1
@Logan与代码无关,但imo自定义视图应支持从Storyboard / XIB加载。我的评论只是对那些想要创建这种观点的人的通知
Nikita Took 2015年

1
请注意,使用第二种形式调用此函数仍然存在问题let myCustomView = UIView.fromNib() as? CustomView。在这种情况下,T.self解析为UIView而不是,CustomView并且它找不到笔尖。我不确定为什么会这样-可能let是将函数的推断类型称为UIView
梯队

2
重要的是要指出,尝试使用File's Owner来连接出口(就像我们过去的日子一样)将导致崩溃。在IB中,文件的所有者必须为nil / empty,并且出口应与视图挂钩。
梯队

1
@Echelon,你救了我的一天!!!我使用文件的所有者连接了出口,但是使用视图却无法正常工作。
Jan Schlorf '18

19

尝试下面的代码。

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中为init())内部调用此方法。
Bagusflyer 2014年

12

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

    }

}

采用

class MyView: UIView, NibInstantiatable {

}

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

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

用法

let view = MyView.fromNib()

3
到目前为止,这是最优雅,最直接的解决方案,我不知道为什么它不是公认的答案!
horseshoe7

@ horseshoe7,因为它是在问题4年后写的。
Freeubi

11

我是通过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 View插座来观看快速定义的插座。您还可以将“第一响应者”设置为自定义类别名称,以开始连接任何其他插座。

希望这可以帮助!


10

在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


9

如果您的项目中有很多自定义视图,则可以创建如下类 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)
    }
}

迅捷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)
    }
}

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

class MyCustomClass: UIViewFromNib {

}

8

基于以上解决方案。

这将适用于所有项目包,并且在调用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)
    }
}

迅捷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")

6

斯威夫特4

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

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

如果您想在任何地方使用

最佳解决方案是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
    }
}

然后这样称呼它:

let myCustomView: CustomView = UIView.fromNib()

5
let subviewArray = NSBundle.mainBundle().loadNibNamed("myXib", owner: self, options: nil)
return subviewArray[0]

但是在Swift的init()中,没有返回值。我忘了提一下,我需要在UIView的初始化中调用loadNibNamed。
Bagusflyer 2014年

您的意思是“无回报”?self从所有init方法隐式返回...
Grimxn 2014年

1
我的意思是我在init方法中调用loadNibNamed。加载的UIView在ObjC中分配给self。但是很快,事实并非如此。
Bagusflyer 2014年

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

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

let view = Views.view1.getView()

2
请注意,如果你这样做有一个空的笔尖文件或榫文件有没有UIView的根节点你会崩溃,你是不理智的位置0检查数组大小或元素
马修·考利

4

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

  1. 我创建了一个XIB和一个名为SomeView的类(为方便起见,使用了相同的名称)。我都基于UIView。
  2. 在XIB中,我将“文件所有者”类更改为SomeView(在身份检查器中)。
  3. 我在SomeView.swift中创建了一个UIView出口,并将其链接到XIB文件中的顶级视图(为方便起见,将其命名为“视图”)。然后,根据需要将其他插座添加到XIB文件中的其他控件。
  4. 在SomeView.swift中,我将XIB加载到initinit:frame: CGRect初始化程序中。无需为“自我”分配任何内容。加载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
      }
    
    
      ...
    }

我更喜欢将init与frame一起使用,所以我连根拔起了!需要注意的一件事...如果您希望视图与您传入的帧相匹配,请添加self.view.frame = frame
Mike E

3

Swift 3版本的Logan的答案

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

3

这是一种使用协议和协议扩展(Swift 4.2)以编程方式加载视图的简洁声明方式:

protocol XibLoadable {
    associatedtype CustomViewType
    static func loadFromXib() -> CustomViewType
}

extension XibLoadable where Self: UIView {
    static func loadFromXib() -> Self {
        let nib = UINib(nibName: "\(self)", bundle: Bundle(for: self))
        guard let customView = nib.instantiate(withOwner: self, options: nil).first as? Self else {
            // your app should crash if the xib doesn't exist
            preconditionFailure("Couldn't load xib for view: \(self)")
        }
        return customView
    }
}

您可以这样使用:

// don't forget you need a xib file too
final class MyView: UIView, XibLoadable { ... }

// and when you want to use it
let viewInstance = MyView.loadFromXib()

其他注意事项

  1. 确保自定义视图的xib文件具有该视图的Custom Class集(以及从那里设置的出口/操作),而不是该文件所有者的集。
  2. 您可以在自定义视图外部或内部使用此协议/扩展。如果初始化视图时还有其他设置工作,则可能需要在内部使用它。
  3. 您的自定义视图类和xib文件必须具有相同的名称。

2

我只是这样做:

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

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

我曾经在上述解决方案中唤醒视图init方法或制作通用方法(顺便说一句很聪明),但是我不再这样做了。

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

我发现让工厂对象(通常使用视图的viewController)根据需要创建它更容易。有时您需要一个所有者(通常是在创建的视图连接到创建者的插座时),有时则不需要。

这可能就是Apple initFromNib在其UIView类中未包含方法的原因。

举一个基本的例子,您不知道自己的出生方式。你刚出生。意见也是如此;)


2

您要做的就是在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
    }
}

现在,如果要将此视图添加为视图控制器中的子视图,请在view controller.swift文件中执行以下操作:

self.view.addSubview(className())

这是一个很好的答案,但是有问题,我将对其进行编辑。
C0mrade

这是我实施的方式。但是你可以即兴创作。在此先感谢@ C0mrade
Alap Anerao

1

与上述某些答案类似,但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)
    }
}

这样可以方便地从自己的笔尖以及其他笔尖/束中加载类。


1

您可以通过情节提要进行此操作,只需添加适当的视图约束即可。您可以通过将您自己的任何视图子类化来轻松实现此目的,例如BaseView

目标C

BaseView.h


/*!
 @class BaseView
 @discussion Base View for getting view from xibFile
 @availability ios7 and later
 */
@interface BaseView : UIView

@end


BaseView.m


#import "BaseView.h"

@implementation BaseView

#pragma mark - Public

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - LifeCycle

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self prepareView];
    }
    return self;
}

#pragma mark - Private

- (void)prepareView
{
    NSArray *nibsArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
    UIView *view = [nibsArray firstObject];

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];
    [self addConstraintsForView:view];
}

#pragma mark - Add constraints

- (void)addConstraintsForView:(UIView *)view
{
    [self addConstraints:@[[NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeBottom
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeBottom
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeTop
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeTop
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeLeft
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeLeft
                                                       multiplier:1.0
                                                         constant:0],
                           [NSLayoutConstraint constraintWithItem:view
                                                        attribute:NSLayoutAttributeRight
                                                        relatedBy:NSLayoutRelationEqual
                                                           toItem:self attribute:NSLayoutAttributeRight
                                                       multiplier:1.0
                                                         constant:0]
                           ]];
}

@end

斯威夫特4

import UIKit

class BaseView : UIView {

    // MARK: - LifeCycle

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        prepareView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        prepareView()
    }

    internal class func xibName() -> String {
        return String(describing: self)
    }

    // MARK: - Private
    fileprivate func prepareView() {
        let nameForXib = BaseView.xibName()
        let nibs = Bundle.main.loadNibNamed(nameForXib, owner: self, options: nil)
        if let view = nibs?.first as? UIView {
            view.backgroundColor = UIColor.clear
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubviewWithConstraints(view, offset: false)
        }
    }
}

UIView+Subview


public extension UIView {
    // MARK: - UIView+Extensions

    public func addSubviewWithConstraints(_ subview:UIView, offset:Bool = true) {
        subview.translatesAutoresizingMaskIntoConstraints = false
        let views = [
            "subview" : subview
        ]
        addSubview(subview)

        var constraints = NSLayoutConstraint.constraints(withVisualFormat: offset ? "H:|-[subview]-|" : "H:|[subview]|", options: [.alignAllLeading, .alignAllTrailing], metrics: nil, views: views)
        constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: offset ? "V:|-[subview]-|" : "V:|[subview]|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views))
        NSLayoutConstraint.activate(constraints)
    }
}

我提供了2种变体如何添加约束-常见约束和可视格式语言中-选择您要的任何一个:)

另外,默认情况下,该xib名称与实现类名称具有相同的名称。如果没有,那就换xibName参数。

如果从中对视图进行子类化BaseView,则可以轻松放置任何视图并在IB中指定类。


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

0

基于洛根答案的更强大版本

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

0

最方便的实现。在这里,您需要两个方法,以便直接返回类的对象,而不是UIView。

  1. viewId标记为,允许覆盖
  2. 您的.xib可以包含多个顶级视图,这种情况也可以正确处理。

extension UIView {

class var viewId: String {
    return String(describing: self)
}

static func instance(from bundle: Bundle? = nil, nibName: String? = nil,
                    owner: Any? = nil, options: [AnyHashable : Any]? = nil) -> Self? {

    return instancePrivate(from: bundle ?? Bundle.main,
                           nibName: nibName ?? viewId,
                           owner: owner,
                           options: options)
}

private static func instancePrivate<T: UIView>(from bundle: Bundle, nibName: String,
                                              owner: Any?, options: [AnyHashable : Any]?) -> T? {

    guard
        let views = bundle.loadNibNamed(nibName, owner: owner, options: options),
        let view = views.first(where: { $0 is T }) as? T else { return nil }

    return view
}
}

例:

guard let customView = CustomView.instance() else { return }

//Here customView has CustomView class type, not UIView.
print(customView is CustomView) // true

0

如果您想让Swift UIView子类完全独立,并且能够使用init或init(frame :)实例化而不暴露使用Nib的实现细节,那么可以使用协议扩展来实现这一点。此解决方案避免了许多其他解决方案所建议的嵌套UIView层次结构。

public class CustomView: UIView {

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var valueLabel: UILabel!

    public convenience init() {
        self.init(frame: CGRect.zero)
    }

    public override convenience init(frame: CGRect) {
        self.init(internal: nil)
        self.frame = frame
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    fileprivate func commonInit() {
    }
}

fileprivate protocol _CustomView {
}

extension CustomView: _CustomView {
}

fileprivate extension _CustomView {

    // Protocol extension initializer - has the ability to assign to self, unlike
    // class initializers. Note that the name of this initializer can be anything
    // you like, here we've called it init(internal:)

    init(internal: Int?) {
        self = Bundle.main.loadNibNamed("CustomView", owner:nil, options:nil)![0] as! Self;
    }
}

0
  let bundle = Bundle(for: type(of: self))
   let views = bundle.loadNibNamed("template", owner: self, options: nil)
    self.view.addSubview(views?[0] as! UIView)

不鼓励仅使用代码的答案。请添加一些有关如何解决问题或与现有答案有何不同的解释。来自评论
尼克

0

我更喜欢下面的扩展

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

此扩展名与顶部回答的扩展名之间的区别是,您无需将其存储为常量或变量。

class TitleView: UIView { }

extension UIView {
    class var instanceFromNib: Self {
        return Bundle(for: Self.self)
            .loadNibNamed(String(describing: Self.self), owner: nil, options: nil)?.first as! Self
    }
}

self.navigationItem.titleView = TitleView.instanceFromNib

您正在使用哪个版本的Xcode?请确保您使用的是最新版本的XCode。使用XCode 11.5(最新版本是最新的)对我来说工作正常。
iCoder

0
    let nibs = Bundle.main.loadNibNamed("YourView", owner: nil, options: nil)
    let shareView = nibs![0] as! ShareView
    self.view.addSubview(shareView)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.