How to embed a UITableView in a custom view
Asked Answered
K

2

6

Goal

I want to create a custom view that has a UITableView as a subview.

enter image description here

The custom view creates the table view programmatically. To the outside world (i.e., the ViewController), though, the custom view itself would appear to be a table view.

What I've tried

import UIKit
class CustomTableView: UIView {
    
    // Do I make outlets?
    //@IBOutlet var dataSource: UITableViewDataSource?
    //@IBOutlet var delegate: UITableViewDelegate?
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        var tableView: UITableView!
        tableView = UITableView(frame: self.bounds)
        // I'm not sure how to set the delegate and dataSource
        // tableView.dataSource = ???
        // tableView.delegate = ???
        
        self.addSubview(tableView)
       
    }
}

After creating the UITableView programmatically and adding it as a subview to the custom view parent, I can't figure out how to get the custom view to act like it is the table view. That is, I don't know how to get the custom view to do the communication between the View Controller and the table view for the delegate and dataSource.

What I've read

These articles seemed good, but I got a little lost.

How do I make the custom view act like it's own table subview with regard to delegate and data source?

Kero answered 28/7, 2015 at 23:41 Comment(2)
Normally the only way a UIView would "appear" to be s table view to a view controller is if it in fact is s subclass of UITableView. Is this what you mean?Jenkins
@algal, in this case I don't mean to subclass UITableView. I'm trying to make a horizontal TableView for vertical Mongolian. The only way I could get auto layout to work when I was doing this for a vertical Mongolian TextView was to wrap it in two container views. See this answer and its question.Kero
K
6

The solution is to make the tableView a property of the custom class (as @BaseZen suggested). Then provide properties and methods in the custom class to mimic and pass along the properties and methods needed from tableView.

import UIKit
@IBDesignable class UICustomTableView: UIView {

    private var myTableView: UITableView

    required init(coder aDecoder: NSCoder) {
        myTableView = UITableView()
        super.init(coder: aDecoder)
    }
    override init(frame: CGRect){
        myTableView = UITableView()
        super.init(frame: frame)
    }
    override func awakeFromNib() {
        super.awakeFromNib()
    }

    // TODO: @IBOutlet still can't be set in IB
    @IBOutlet weak var delegate: UITableViewDelegate? {
        get {
            return myTableView.delegate
        }
        set {
            myTableView.delegate = newValue
        }
    }

    // TODO: @IBOutlet still can't be set in IB
    @IBOutlet weak var dataSource: UITableViewDataSource? {
        get {
            return myTableView.dataSource
        }
        set {
            myTableView.dataSource = newValue
        }
    }

    func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String) {
        myTableView.registerClass(cellClass, forCellReuseIdentifier: identifier)
    }

    func dequeueReusableCellWithIdentifier(identifier: String) -> UITableViewCell? {
        return myTableView.dequeueReusableCellWithIdentifier(identifier)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // ...

        // setup UITableView
        myTableView.frame = self.bounds
        self.addSubview(myTableView)

        // ...
    }       
}

Then it can be used like a normal UITableView:

import UIKit
class TableViewDemoVC: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var customTableView: UICustomTableView!

    var items: [String] = ["One", "Two", "Three"]

    override func viewDidLoad() {
        super.viewDidLoad()

        // setup the table view from the IB reference
        customTableView.delegate = self
        customTableView.dataSource = self
        customTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")

    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell: UITableViewCell = self.customTableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell!
        cell.textLabel?.text = self.items[indexPath.row]            
        return cell
    }

    // ...
}
Kero answered 29/7, 2015 at 20:23 Comment(0)
C
2

It's not the most elegant, but jump out a level:

class MyCustomViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    override func viewDidLoad() {
        myCustomView = CustomTableView()
        myCustomView.frame = ... /* layout programatically */
        /* Alternatively to the above 2 lines, 
           lay out myCustomView in StoryBoard,
           and capture it as an @IBOutlet. It will then be ready here
           to muck with, well before it gets displayed and needs the data */
        myCustomView.tableView.delegate = self
        myCustomView.tableView.dataSource = self
    }

    /* Now implement all your dataSource and delegate methods */
}

* IMPORTANT *

The key that you're missing is that tableView must be a stored property of your custom view. It's important and needs to be promoted from a silly local! It should also be initialized in the awakeFromNib() function, even if you don't know the frame size. Then reset its frame at layout time.

At a higher level, I don't know "What You're Really Trying To Do." It may not actually be the right implementation technique to be embedding a UITableView within a custom view; consider just laying out the UITableView in the main view. Then if you need decoration around it, lay those out as separate views in StoryBoard.

Chap answered 28/7, 2015 at 23:58 Comment(3)
Thank you. I will play around with making tableView a property. Ideally I would like to take out the middle layer in myCustomView.tableView.delegate and just do myCustomView.delegate. What I am really trying to do is make a horizontally scrolling table view for use with traditional Mongolian. See this and this question. I'm trying to make it behave just like a regular UITableView.Kero
In that case subclass UITableView and put your custom code there, rather than wrapping it with a trivial layer that adds nothing. Be sure to update the class setting to your custom subclass in StoryBoard's Identity Inspector. I might ask why get hung up on TableView? Rotating it 90 degrees is easy but causes oddities like callout indicators appear at the top or bottom which feels wrong. Instead why not use UICollectionView? It's more work up front but you just use a flow layout and get lots of layout flexibility.Chap
I'll look into UICollectionView. The reason I can't just rotate (and flip) a subclassed TableView is that auto layout no longer works on a view that has been transformed. Using two container views is a workaround for that, albeit less than ideal.Kero

© 2022 - 2024 — McMap. All rights reserved.