【问题标题】:How to initialize/instantiate a custom UIView class with a XIB file in Swift如何在 Swift 中使用 XIB 文件初始化/实例化自定义 UIView 类
【发布时间】:2014-10-20 05:14:50
【问题描述】:

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

class MyClass: UIView {

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

【问题讨论】:

标签: ios swift


【解决方案1】:

我测试了这段代码,效果很好:

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
}

【讨论】:

  • 它应该是 var view = MyClass.instanceFromNib() & self.view.addSubview(view) 而不是 var view = MyClass.instanceFromNib & self.view.addSubview(view())。只是一个改善答案的小建议:)
  • 在我的情况下,视图稍后初始化!如果是 self.view.addSubview(view) , view 应该是 var view = MyClass.instanceFromNib()
  • @Ezimet 该视图中的 IBActions 怎么样。在哪里处理它们。就像我的视图中有一个按钮(xib)如何处理该按钮的 IBAction 点击​​事件。?
  • 是否应该返回“MyClass”而不仅仅是 UIView?
  • IBoutlets 在这种方法中不起作用...我得到:“这个类不符合键的键值编码”
【解决方案2】:

Sam 的解决方案已经很棒了,尽管它没有考虑不同的包(NSBundle:forClass 来拯救)并且需要手动加载,也就是输入代码。

如果您想全面支持您的 Xib Outlets、不同的捆绑包(在框架中使用!)并在 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 子类化您自己的 View 类并在 Xib 文件中将类名设置为 File's Owner

不再需要额外的代码。

信用到期的信用:与 GH 上的 DenHeadless 进行了细微的更改。我的要点:https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

【讨论】:

  • 此解决方案会阻止插座连接(因为它将加载的视图添加为子视图),如果将 NibLoadingView 嵌入 XIB 中,从 init?(coder:) 调用 nibSetup 将导致无限递归。
  • @redent84 感谢您的评论和反对。如果你有第二次看,它应该替换以前的 SubView(没有给出新的实例变量)。你对无限递归是正确的,如果与 IB 斗争,应该省略它。
  • 如前所述“将 Outlets 连接到 File Owner 并将 File Owner 类设置为您自己的类。”将插座连接到文件所有者
  • 我总是对使用这种方法从 xib 加载视图感到不舒服。我们基本上是在一个也是 A 类子类的视图中添加一个视图,它是 A 类的子类。难道没有什么方法可以防止这种迭代吗?
  • @PrajeetShrestha 这可能是由于 nibSetup() 将背景颜色覆盖为.clearColor() - 从情节提要加载后。但是,如果在实例化后通过代码执行此操作,它应该可以工作。无论如何,如前所述,一种更优雅的方法是基于协议的方法。当然,这里有一个链接:github.com/AliSoftware/Reusable。我现在正在对 UITableViewCells 使用类似的方法(我在发现那个真正有用的项目之前就实现了它)。 hth!
【解决方案3】:

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

}

【讨论】:

  • 这是一个比所选答案更好的解决方案,因为不需要强制转换,并且它也可以在您将来创建的任何其他 UIView 子类中重复使用。
  • 我在 Swift 2.1 和 Xcode 7.2.1 上试过这个。它有时会起作用,并且会在其他人使用互斥锁时意外挂断。代码中直接使用的最后两行每次都有效,最后一行修改为var myView = nib.instantiate... as! myViewType
  • @jr-root-cs 您的编辑包含拼写错误/错误,我不得不将其回滚。无论如何,请不要将代码添加到现有答案中。相反,发表评论;或添加您的版本在您自己的答案中。谢谢。
  • @jr.root.cs 是的,这个答案很好,这就是为什么没有人应该改变它。这是山姆的答案,不是你的。如果您想对此发表评论,请发表评论;如果您想发布新的/更新的版本,请在您自己的帖子中。编辑旨在修复拼写错误/缩进/标签,而不是在其他人的帖子中添加您的版本。谢谢。
  • 您也可以使用let nibName = String(describing: Self.self) (Swift 3) 获取类的名称
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    从 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 子类的类型的输出视图对我的最佳回答
    【解决方案6】:

    斯威夫特 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. 创建我的自定义视图

      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.")
      

    【讨论】:

    • 如果自定义视图中有按钮,我们如何在不同的视图控制器中处理它们的动作?
    • 您可以使用协议委托。看看这里stackoverflow.com/questions/29602612/…
    • 在xib中加载图片失败,出现如下错误。无法加载从标识符为“test.Testing”的包中的笔尖引用的“IBBrokenImage”图像
    【解决方案7】:

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

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

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

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

    实例化它:

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

    【讨论】:

    • 如果没有设置所有者,我会返回“加载了“MyClas”笔尖但未设置视图出口。'”
    【解决方案8】:

    Swift 5.3

    创建一个名为NibLoadingView 的类,其内容如下:

    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 public weak var view: UIView!
        
        private var didLoad: Bool = false
    
        public override init(frame: CGRect) {
            super.init(frame: frame)
            
            self.nibSetup()
        }
    
        required public init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            
            self.nibSetup()
        }
        
        open override func layoutSubviews() {
            super.layoutSubviews()
            
            if !self.didLoad {
                self.didLoad = true
                self.viewDidLoad()
            }
        }
        
        open func viewDidLoad() {
            self.setupUI()
        }
    
        private func nibSetup() {
            self.view = self.loadViewFromNib()
            self.view.frame = bounds
            self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            self.view.translatesAutoresizingMaskIntoConstraints = true
    
            addSubview(self.view)
        }
    
        private func loadViewFromNib() -> UIView {
            guard let nibName = type(of: self).description().components(separatedBy: ".").last else {
                fatalError("Bad nib name")
            }
            
            if let defaultBundleView = UINib(nibName: nibName, bundle: Bundle(for: type(of: self))).instantiate(withOwner: self, options: nil).first as? UIView {
                return defaultBundleView
            } else {
                fatalError("Cannot load view from bundle")
            }
        }
    }
    

    现在创建一个 XIB 和 UIView 类对,将 XIB 的所有者设置为 UIView 类和子类NibLoadingView

    您现在可以像 ExampleView()ExampleView(frame: CGRect) 等一样或直接从情节提要中初始化类。

    您也可以像在UIViewController 中一样使用viewDidLoad。在那一刻呈现您所有的插座和布局。

    基于Frederik's answer

    【讨论】:

    • 这个观点的关键点。translatesAutoresizingMaskIntoConstraints = true
    【解决方案9】:

    如果有人想以编程方式使用 XIB 加载自定义视图,下面的代码将完成这项工作。

    let customView = UINib(nibName:"CustomView",bundle:.main).instantiate(withOwner: nil, options: nil).first as! UIView
    customView.frame = self.view.bounds
    self.view.addSubview(customView)
    

    【讨论】:

      【解决方案10】:
      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)
      
              })
          })
      
      
      }
      

      【讨论】:

      • 格式错误的代码,没有解释,因此投反对票。
      • 这和这个问题有什么关系?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-15
      • 2011-06-02
      • 2014-08-11
      • 2023-03-19
      • 1970-01-01
      相关资源
      最近更新 更多