如何在Swift中使用XIB文件初始化/实例化自定义UIView类


139

我有一个称为MyClass的子类UIView,我想用一个XIB文件初始化。我不确定如何使用名为xib的文件初始化此类View.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}

5
参考用于iOS9夫特完整的源代码示例2.0 github.com/karthikprabhuA/CustomXIBSwift 和相关线程stackoverflow.com/questions/24857986/...
karthikPrabhu Alagu

Answers:


266

我测试了这段代码,它很好用:

class MyClass: UIView {        
    class func instanceFromNib() -> UIView {
        return UINib(nibName: "nib file name", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
    }    
}

初始化视图并按如下方式使用它:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

要么

var view = MyClass.instanceFromNib
self.view.addSubview(view())

更新Swift> = 3.x和Swift> = 4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}

1
应该是var view = MyClass.instanceFromNib()self.view.addSubview(view)而不是var view = MyClass.instanceFromNibself.view.addSubview(view())。只是改善建议的一个小建议:)
Logan

1
就我而言,稍后初始化视图!如果是self.view.addSubview(view),则视图应为var view = MyClass.instanceFromNib()
Ezimet 2014年

2
@Ezimet在该视图中的IBActions呢?在哪里处理它们。就像我的视图(xib)中有一个按钮一样,如何处理该按钮的IBAction单击事件。
加迪·侯赛因

6
它应该返回“ MyClass”而不是仅返回UIView吗?
谢克松(Kesong Xie)

2
IBoutlet无法以这种方式工作...我得到了:“该类不符合密钥的键值编码要求”
Radek Wilczak

82

尽管Sam的解决方案没有考虑不同的捆绑软件(NSBundle:forClass可以解决),但它的解决方案已经非常出色,并且需要手动加载,也就是键入代码。

如果要全面支持Xib Outlet,不同的Bundle(在框架中使用!),并在Storyboard中获得不错的预览,请尝试以下操作:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

照常使用您的xib,即将Outlets连接到File Owner,并将File Owner类设置为您自己的类。

用法:从NibLoadingView和类名设置只要继承自己的视图类文件的所有者在XIB文件

不再需要其他代码。

贷项到期的贷项:与GH上的DenHeadless进行了细微的更改,从而进行了分叉。我的要旨:https : //gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8


8
此解决方案会制动出口连接(因为它将加载的视图添加为子视图),并且如果将其嵌入到XIB中,则调用nibSetupfrom init?(coder:)会导致无限递归NibLoadingView
redent84'9

3
@ redent84感谢您的评论和推荐。如果您有第二种外观,则应替换之前的SubView(未提供新的实例变量)。您对无限递归是正确的,如果与IB纠缠,应将其省略。
Frederik Winkelsdorf

1
如前所述,“将插座连接到文件所有者,并将文件所有者类设置为您自己的类”。将出口连接到文件所有者
Yusuf X

1
对于使用此方法从xib加载视图,我总是感到不舒服。我们基本上是在也是A类子类的视图中添加一个视图,该类是A类的子类。难道没有某种方法可以防止这种迭代吗?
Prajeet Shrestha

1
@PrajeetShrestha这可能是由于.clearColor()从情节提要加载后,nibSetup()将背景颜色覆盖为-的缘故。但是,如果在实例化之后通过代码执行此操作,则它应该可以工作。无论如何,如前所述,一种更为优雅的方法是基于协议的方法。因此,可以肯定的是,这里有您的链接:github.com/AliSoftware/Reusable。现在,我针对UITableViewCells使用了类似的方法(在发现真正有用的项目之前就已实现了该方法)。hth!
Frederik Winkelsdorf

77

从Swift 2.0开始,您可以添加协议扩展。我认为这是一种更好的方法,因为返回类型Self不是UIView,所以调用者不需要转换为视图类。

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}

4
这是比选择的答案更好的解决方案,因为它不需要强制转换,并且将来可以在您创建的任何其他UIView子类中重用。
user3344977

3
我用Swift 2.1和Xcode 7.2.1尝试过。它有时会起作用,并且使用互斥锁会无法预料地挂在其他人身上。每次在代码中直接使用的最后两行都有效,最后一行修改为var myView = nib.instantiate... as! myViewType
Paul Linsay

@ jr-root-cs您的编辑包含错别字/错误,我不得不将其回滚。而且无论如何,请不要在现有答案中添加代码。相反,请发表评论。或在您自己的答案中添加您的版本。谢谢。
埃里克·艾雅

我发布了使用Swift 3(XCode 8.0 beta 6)在项目中打开并测试的代码,没有出现问题。错字了Swift 2。当此答案很好并且用户使用XC8时,用户可能希望搜索哪些变化时,为什么还要选择另一个答案
jr.root.cs

1
@ jr.root.cs是的,这个答案很好,这就是为什么没有人应该更改它的原因。这是山姆的答案,而不是你的答案。如果您想对此发表评论,请发表评论;如果您要发布新的/更新的版本,请在自己的文章中进行。编辑是为了修正错别字/缩进/标签,不要在其他人的帖子中添加您的版本。谢谢。
埃里克·艾雅

37

这就是Frederik在Swift 3.0上的答案

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}

31

从xib加载视图的通用方法:

例:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

实现方式:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}

作为UIView子类的类型的输出视图,这对我来说是最好的答案
jfgrang

26

Swift 3回答:就我而言,我想在我的自定义类中有一个可以修改的插座:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!

    class func createMyClassView() -> MyClass {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

在.xib中时,请确保“自定义类”字段为MyClassView。不要打扰文件的所有者。

确保自定义类是MyClassView

另外,请确保将MyClassView中的插座连接到标签: myLabel的插座

实例化它:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"

如果未设置所有者,我会返回“已加载“ MyClas”笔尖,但未设置视图出口。”
jcharch19 '19

22

斯威夫特4

在这种情况下,我必须将数据传递到该自定义视图中,因此我创建了静态函数来实例化该视图。

  1. 创建UIView扩展

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

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
  3. 在.xib文件中将自定义类设置为MyCustomView。照常连接插座。 在此处输入图片说明

  4. 实例化视图

    let view = MyCustomView.instantiate(message: "Hello World.")

如果自定义视图中有按钮,我们如何在不同的视图控制器中处理它们的动作?
jayant rawat

您可以使用协议委托。在这里看看stackoverflow.com/questions/29602612/…
助记符23

无法在xib中加载图像,出现以下错误。无法加载从标识符为“ test.Testing ”的捆绑包中的笔尖引用的“ IBBrokenImage ”图像
Ravi Raja Jangid

-4
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

class func instanceFromNib() -> LAAlertView {
    return UINib(nibName: "LAAlertView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! LAAlertView
}

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}

格式错误的代码,没有解释,因此不赞成。
J. Doe,

这一切与这个问题有什么关系?
阿什莉·米尔斯
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.