简体   繁体   English

从xib加载视图的正确方法是什么?

[英]What is the correct way to load view from xib?

All of the resources I have found so far suggest variations of this code for loading a view from xib file, but always implementing the same logic: 到目前为止,我发现的所有资源都提出了用于从xib文件加载视图的此代码的变体,但始终实现相同的逻辑:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    var view = (Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)![0])
    self.addSubview(view as! UIView)
}

Which doesn't look right to me because this function belongs to a subclass of UIView, and adds the intended design ( xib 's design) as a subview. 在我看来,这不太合适,因为此函数属于UIView的子类,并将所需的设计( xib的设计)添加为子视图。 In this case if we added a different UI element to self , it would belong to a different parent than the UI elements in the xib file. 在这种情况下,如果我们将不同的UI元素添加到self ,则它将属于与xib文件中的UI元素不同的父xib

So the hierarchy would look like this: 因此,层次结构如下所示:

---self (UIView)  
    ---Other UI elements added by code in `self`
    ---xib (UIView)
        ---UI elements in xib file

Isn't this an unnecessary wrapping? 这不是不必要的包装吗? If so, is there a way to add all the views in xib file to self directly? 如果是这样,是否有办法将xib文件中的所有视图直接添加到self Or is there a different more elegant way of loading design from a xib file? 还是从xib文件中加载设计的方式有其他更优雅的方式?

One way to reuse a complex set of subviews is to define an embedded view controller. 重用一组复杂的子视图的一种方法是定义一个嵌入式视图控制器。 You start by defining your top-level view controller. 首先定义顶级视图控制器。 In it, you define an outlet and connected it to an instance of your sub-controller (also defined in the nib). 在其中,您定义了一个插座并将其连接到子控制器的一个实例(也在笔尖中定义)。 You also connect the sub-controller's view to a placeholder UIView in the top-level nib's view hierarchy. 您还可以将子控制器的view连接到顶级笔尖的视图层次结构中的占位符UIView

class ViewController: UIViewController {

    @IBOutlet var childController: ReusableViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}

The slight-of-hand occurs in the sub-controller. 轻微的作用发生在子控制器中。 Its awakeFromNib function gets invoked when the super-controller is loaded. 加载超级控制器时,将调用其awakeFromNib函数。 The sub-controller then uses the "placeholder" UIView it is connected to to insert it's view hierarchy into the top-level views. 然后,子控制器使用与其连接的“占位符” UIView将其视图层次结构插入到顶级视图中。

class ReusableViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        // This controller manages a reusable view defined by a seperate nib.
        // When defined in the parent nib, its view is connected to a placeholder view object.
        // When the nib is loaded, the secondary nib is loaded and replaces the placeholder.
        let placeholder = self.view!
        Bundle.main.loadNibNamed("ReusableViewController", owner: self, options: nil)
        // (the nib connects the view property to the actual view, replacing the placeholder)
        placeholder.superview?.insertSubview(self.view, aboveSubview: placeholder)
        // (do something about constraints here?)
        placeholder.removeFromSuperview()
    }

}

The advantage to this arrangement is that the sub-controller can have whatever bindings, outlets, actions, connections, properties, and business logic that it needs, neatly encapsulated and reusable. 这种安排的优点是,子控制器可以具有它需要的任何绑定,出口,动作,连接,属性和业务逻辑,可以整齐地封装和重用。

This is a bit of a hack because it short-circuits the sub-controller's view-did-load lifecycle (because the controller never loads its own view). 这有点骇人听闻,因为它会使子控制器的view-did-load生命周期短路(因为控制器从不加载自己的视图)。 To address that, you could define another property that pointed the sub-controller at either a placeholder view or the container view that is should insert itself into. 为了解决这个问题,您可以定义另一个属性,该属性将子控制器指向应该插入自身的占位符视图或容器视图。 Then replace the Bundle.main.loadNibName(blah, blah, blah) stuff with just let replacement = self.view and let the view controller do all of the loading. 然后更换Bundle.main.loadNibName(blah, blah, blah)的东西只let replacement = self.view ,让视图控制器做所有的负荷。


Here's a finished solution using storyboards contributed by the original poster after getting this to work 这是使用原始海报提供的情节提要的最终解决方案,使之生效

Using the reusable view in storyboard 在情节提要中使用可重用视图
Add a Container View (Not to confuse with View ) to ViewController (your main view on storyboard), and set it's scene's (controller) class to the controller we just defined ( ReusableViewController ). Container View (不要与View混淆)添加到ViewController (情节ViewController的主视图),并将其场景的(控制器)类设置为我们刚刚定义的控制器( ReusableViewController )。
GIF
That's it. 而已。 This way you can add as many subviews to a view without having to wrap your subviews in storyboards as well as in code. 这样,您可以向视图中添加尽可能多的子视图,而不必将子视图包装在情节提要和代码中。
Container View 's intended use is actually documented as exactly for this purpose here Container View的用途实际上是记录为正是为此目的在这里

Generally when you implement a xib for a view , it always contains all the needed elements for it . 通常,在为视图实现xib时,该xib始终包含该视图所需的所有元素。 but if you have you can try to add the element like this 但是如果有的话,您可以尝试添加这样的元素

var view = (Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)![0]) as! UIView

var lbl = UILabel()

lbl.frame  = //.....

view.addSubview(lbl)

self.addSubview(view)

You can load the xib of a view during init: 您可以在初始化期间加载视图的xib:

init() {
    super.init(nibName: "ViewController", bundle: Bundle(identifier: "domain.AppName")!)
    self.loadView()
}

Is that what your are looking for? 那是您要找的东西吗?

Here's the answer you've wanted all along. 这就是您一直想要的答案。 You can just create your CustomView class, have the master instance of it in a xib with all the subviews and outlets. 您可以只创建CustomView类,将其主实例包含在带有所有子视图和出口的xib中。 Then you can apply that class to any instances in your storyboards or other xibs. 然后,您可以将该类应用于情节提要或其他xib中的任何实例。

No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself. 无需摆弄File的所有者,或将插座连接到代理或以特殊的方式修改xib,或将自定义视图的实例添加为其自身的子视图。

Just do this: 只要这样做:

  1. Import BFWControls framework 导入BFWControls框​​架
  2. Change your superclass from UIView to NibView (or from UITableViewCell to NibTableViewCell ) 将您的超类从UIView更改为NibView (或从UITableViewCell更改为NibTableViewCell

That's it! 而已!

It even works with IBDesignable to refer your custom view (including the subviews from the xib) at design time in the storyboard. 它甚至可以与IBDesignable一起使用,以在设计时在情节提要中引用您的自定义视图(包括来自xib的子视图)。

You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155 您可以在此处了解更多信息: https : //medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls 您可以在此处获取开源的BFWControls框​​架: https : //github.com/BareFeetWare/BFWControls

And here's a simple extract of the NibReplaceable code that drives it, in case you're curious: https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4 这是驱动它的NibReplaceable代码的简单摘录,以防您好奇: https : NibReplaceable

Tom 👣 汤姆👣

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM