mike
mike

Reputation: 2193

Programatically adding custom view to another view

I've created a custom view using a .xib file for the layout. I would like to programatically create and add this view as a subview to a different view.

Subview class:

class Subview: UIView {

@IBOutlet var view: UIView!
@IBOutlet var titleLabel: UILabel!
@IBOutlet var textLabel: UILabel!

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

    Bundle.main.loadNibNamed("Subview", owner: self, options: nil)
    self.addSubview(view)
    view.frame = self.bounds
  }
}

I'm not sure how to go about it, the only constructor currently expects an instance of NSCoder which I don't have.

I've tried to give it an additional initialiser:

init(title: String, text: String) {
    titleLabel.text = title
    textLabel.text = text
    super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}

And then call it:

let v = Subview(title: dataSource[i].whatever, text: dataSource[i].bla)

But it fails when trying to unwrap the label outlets, which makes sense as the .xib hasn't been initialised.

I've already tried using a tableview and custom cells instead of programatically building the UI for the page, it doesn't really work because this is a detail page and I only have a single object as my data source instead of the array which the rowForIndexPath tableview delegate expects. I know I could write some mapper method to turn the object into an array of it's properties but I'd rather avoid that if possible.

Thanks.

Upvotes: 2

Views: 14007

Answers (3)

timaktimak
timaktimak

Reputation: 1380

1) Go to Subview.xib and make sure that the file owner is left empty.

enter image description here

Also, make sure that the class for your view is selected as Subview.

enter image description here

2) Get rid of the initializer, so:

class Subview: UIView {

    @IBOutlet var view: UIView!
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var textLabel: UILabel!

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

        Bundle.main.loadNibNamed("Subview", owner: self, options: nil)
        self.addSubview(view)
        view.frame = self.bounds
    }
}

should be:

class Subview: UIView {

    @IBOutlet var view: UIView!
    @IBOutlet var titleLabel: UILabel!
    @IBOutlet var textLabel: UILabel!
}

3) Initialize you view this way:

let view = UINib(nibName: "Subview", bundle: nil).instantiate(withOwner: nil, options: nil).first as! Subview

You can also check out these.

http://supereasyapps.com/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6

How to initialise a UIView Class with a xib file in Swift, iOS

Good luck!

Upvotes: 4

DonMag
DonMag

Reputation: 77477

Custom subclasses can be tricky, particularly when using xib files. With the code you've shown, it's not clear where you're going wrong - but yeah, probably losing the Outlet connections.

Here's a working example - includes Obj-C and Swift versions, including using the custom classes within Interface Builder. Specifically for what you're trying to do, look at the Swift3/SW3LoadFromXIB example:

https://github.com/DonMag/IBDesignInspect

Upvotes: 0

hermannb
hermannb

Reputation: 58

If you need to handle IBOutlets I suggest you use a UIViewController as the owner of the xib instead of a UIView (when creating a UIViewController from File menu there is the option to create also a xib)

Then you can instantiate it this way in the parent view

    var subviewController: SubviewController?

    override func viewDidLoad() {
        super.viewDidLoad()

        subviewController = SubviewController(nibName: nil, bundle: nil)

        self.view.addSubview(subviewController!.view)

        //Remember to call the IBOutlets only after adding the view
        subviewController!.titleLabel.text = "This is the title" 

        subviewController!.view.frame = self.view.frame //Not suggested
        //Instead set constraints here
    }

Either way you have to set constraints or end using viewDidLayoutSubviews() as suggested in Swift: Add a xib into a UIView

Upvotes: 1

Related Questions