Search code examples
xcodeuiviewswift3loadnibnamed

loadNibNamed returns the view, not "the view"?


I have a small xib, Teste.xib

enter image description here

owned by TesteView

class TesteView: UIView {
    @IBOutlet var tf:UITextField!
    @IBOutlet var sw:UISwitch!
    }

Now, we're gonna load it (and for example stuff it in a stack view).

let t:TesteView = TesteView()
let v = Bundle.main.loadNibNamed("Teste", owner: t, options: nil)?[0] as! UIView
v.heightAnchor.constraint(equalToConstant: 200).isActive = true
stack?.insertArrangedSubview(v, at: 3)

in fact, that's fine.

Everything works.

But note that you insert "v", not "t". "v" is not "the TesteView", it's just some damned view that is floating around.

If you do the following,

t.heightAnchor.constraint(equalToConstant: 200).isActive = true
stack?.insertArrangedSubview(t, at: 3)

it is meaningless, that doesn't work.

But t "is" the view, it's a UIView (indeed, it is a TesteView). It should be the thing you insert.

So you have to use the "two different" things ...

t.tf.text = "WTF???"
// use "t" for that sort of thing
v.heightAnchor.constraint(equalToConstant: 200).isActive = true
v.backgroundColor = UIColor.blue
// but use "v" for that sort of thing
stack?.insertArrangedSubview(v, at: 3)

It seems weird that "t" and "v" are not the same.

(Indeed, should TesteView even have been a subclass of UIView? Maybe it should be something else - just a plain class?? It seems one can not really use it as a view so WTF is it??)

What's the deal on this and/or what is the usual idiom?


NOTE ...

nowadays, there is no reason to ever do this. Just use a small UIViewController. For decades everyone said "Why doesn't apple let you simply load a view controller by id?", well now you can. No problems:

    let t = self.storyboard?.instantiateViewController(withIdentifier: "TesteID") as! Teste
    t.view.heightAnchor.constraint(equalToConstant: 200).isActive = true
    stack?.insertArrangedSubview(t.view, at: 3)
    t.tex.text = "WTH???"

Solution

  • This code makes no sense; your use of t is pointless:

    let t:TesteView = TesteView()
    let v = Bundle.main.loadNibNamed("Teste", owner: t, options: nil)?[0] as! UIView
    

    The object of nominating an owner is to provide some already existing instance with properties that match outlets in the nib. This allows the outlets to be used, and the views to which they lead can immediately be referred to by name.

    That is what happens when a view controller / view pair is loaded from a storyboard, and is why storyboards work the way they do. The view nib is loaded with the view controller as owner. So if the view nib has outlet mySwitch and the view controller has outlet property mySwitch, they match up and the term self.mySwitch can be used by the view controller instance to refer to the switch.

    You can arrange the same thing yourself when loading a .xib file. But you are not doing that; your code is just deliberately silly.

    For example (this is from my book, and you can download and run the example for yourself):

    class ViewController: UIViewController {
        @IBOutlet var coolview : UIView!
        override func viewDidLoad() {
            super.viewDidLoad()
            Bundle.main.loadNibNamed("View", owner: self)
            self.view.addSubview(self.coolview)
        }
    }
    

    If you have configured View.xib with its File's Owner proxy's class set to ViewController, and if you've then drawn an outlet from the File's Owner to the view and named that outlet coolview, when we load it is matched with the coolview property and the last line works — we can refer to the nib-loaded view as self.coolview.

    And notice, please, that we never had to capture the result of loading the nib as an array of views and then refer to element zero of that array, as you do. The view gets slotted right into the name coolview. That is how you load a nib so as to get a useful name or names.