9

I have a segmented control which switches between two UIViewControllers. A info view, and a list view (TableView).

I want to use the first UIViewController as the first cell of my TableView (that is on the other segment).

Is there a way to convert a UIViewController to a cell or someway to use it as a cell for a TableView?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
WitaloBenicio
  • 3,395
  • 5
  • 25
  • 32
  • Yes you can add the view of the VC as a subview of the contentView of the cell. You also need to add the VC as a child VC to some parent VC. Position the VCs view with autolayout. – Sajjon Sep 18 '16 at 20:36
  • Thanks. How can i add my InfoViewController as a child? For what parent. I didn't get very well this part. – WitaloBenicio Sep 18 '16 at 20:45

3 Answers3

17

Use this code with your own way. Here we are adding the controllers view as subview of cell and using auto layout to manage properly. You just need to use the code by understanding.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath)
    cell.layoutIfNeeded()

    let infoVC = self.storyboard.instantiateViewControllerWithIdentifier("InfoVC")
    self.addChildViewController(infoVC)
    cell.contentView.addSubview(infoVC.view)

    infoVC.view.translatesAutoresizingMaskIntoConstraints = false
    cell.contentView.addConstraint(NSLayoutConstraint(item: infoVC.view, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0.0))
    cell.contentView.addConstraint(NSLayoutConstraint(item: infoVC.view, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0.0))
    cell.contentView.addConstraint(NSLayoutConstraint(item: infoVC.view, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0.0))
    cell.contentView.addConstraint(NSLayoutConstraint(item: infoVC.view, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0.0))

    infoVC.didMoveToParentViewController(self)
    infoVC.view.layoutIfNeeded()
}
Mahesh Agrawal
  • 3,348
  • 20
  • 34
  • 3
    This code assumes that the VC owning the UITableView is the DataSource – Sajjon Sep 18 '16 at 21:12
  • 1
    i just answered what you suggested @Sajjon because i had also the same idea by reading what the question asker want to do. you can correct the answer if anything wrong. – Mahesh Agrawal Sep 18 '16 at 21:20
  • This seems to work. But I will try. Just let me ask you one thing. The other cells of my tableview has a different size of the infoVC. How can i handle that? For example...my InfoVC has height of 300, while my other cells has a height of 70. – WitaloBenicio Sep 18 '16 at 21:27
  • you need to add if else and return required value in heightForRowAt'indexPath or you need to use `self.tableView.rowHeight = UITableViewAutomaticDimension` and `self.tableView.estimatedRowHeight = 70.0` and you need to manage constraints inside your vc very well. – Mahesh Agrawal Sep 18 '16 at 21:30
  • If i use the automaticDimension, my other cells will get the height of 300. – WitaloBenicio Sep 18 '16 at 21:32
  • no, if you use minimum cell height for estimatedRowHeight, your other cell will take that minimum height and will increase according ti the constraints added in its subviews. – Mahesh Agrawal Sep 18 '16 at 21:34
  • Thanks. I was trying here with a simple project. And this seems to work very well. I will try in my project and i will back here to mark as accepted answer. Thanks in a advance :) – WitaloBenicio Sep 18 '16 at 21:37
  • welcome.. if anything goes wrong, post the sample. so it will be easy to debug and to give you appropriate solution. – Mahesh Agrawal Sep 18 '16 at 21:39
  • 5
    You may want to consider adding some code to account for the situation where a cell is reused. The previous view controller should be removed, prior to adding a new one. When this is done, the previous VC should have `willMove(toParentViewController: nil)` called along with `removeFromParentViewController()` – user3847320 May 23 '18 at 19:15
  • @user3847320 Can you elaborate? I don't fully understand what you're saying. I understand that removeFromParentViewController() should be called from the previousVC class, but in what method? – mdimarca Sep 30 '18 at 04:31
  • 2
    `UITableViewCell` has a method `prepareForReuse()` that is called before a cell is reused. Overriding this method and calling `removeFromParentViewController()` on any `UIViewController` contained within, is a viable option. – user3847320 Oct 01 '18 at 14:36
4

Swift 5.0 Syntax

let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for:indexPath)
cell.layoutIfNeeded()

let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ViewControllerNameHere")

self.addChild(vc)
cell.contentView.addSubview(vc.view)

vc.view.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addConstraint(NSLayoutConstraint(item: vc.view, attribute: NSLayoutConstraint.Attribute.leading, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.leading, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: vc.view, attribute: NSLayoutConstraint.Attribute.trailing, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.trailing, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: vc.view, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
cell.contentView.addConstraint(NSLayoutConstraint(item: vc.view, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: cell.contentView, attribute: NSLayoutConstraint.Attribute.bottom, multiplier: 1.0, constant: 0.0)) 

vc.didMove(toParent: self)
vc.view.layoutIfNeeded()

return cell
Johno2110
  • 1,071
  • 1
  • 13
  • 25
0

Well, i'd recomend to create a custom UITableViewCell with its own nib file and load it from nib.

This can than added/dequeud to a UITableViewSection. The Cell itself can then be design inside of the UI Builder in any way you want it to look like. You can put literaly everything into the cell you like.

Here is a short tutorial how todo so: using-nib-xib-files

A question to that topic can be find here: Custom UITableViewCell from nib in Swift

Community
  • 1
  • 1
bemeyer
  • 6,154
  • 4
  • 36
  • 86
  • 1
    I could work this way. The problem is that i already have my layout with all the constraints in a storyboard. There is a way to "convert" it to a nib (or xib) file? – WitaloBenicio Sep 18 '16 at 21:24
  • Sorry to tell you that, but i think there is no automatic way todo so. I think you might be able to copy stuff over to the nib/xib file. I think that would be a clean solution to create a custom cell like this. Moreover it makes it easy to maintain and you dont have to fiddle with setting constrains in code. You can try to use a `ContainerViewController` to load the other storyboad into the xib/nib cell. (This is for sure a dirty hack and i wouldnt recomend it!) https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html – bemeyer Sep 18 '16 at 21:29