How to put UITableView inside a UIView

15,275

Here's the shake down, full project, and it works. The first view controller pops up, you see some buttons, I just used a tableview with some random names, you click the names, and another tableview pops up with the title of the table the same name as the cell you clicked on the first view Controller. This works through using blocks, no delegation in the UIView, but I put everything in a custom view except for the first viewcontroller's view which is created with the "loadView" method. As you can see, no data is passed from the UIView into the UITableViewCells, you don't do it this way, you pull in all data from the first view controller's cells and assuming that the first viewcontroller's table cells have a TON of data form a JSON array or something like this then you can pass that entire "scores array" to the next view controller through a property declaration variable, I'm just showing you how to pass information using the "title" property of a UIViewController, you can create your own properties and pass an entire array when the button of a cell or regualr button is clicked. You don't want to put this information into you Subclassed tableViewCell, but you can PASS data from the secondViewController's cellForRowAtIndexPath to set custom information for the scoreboard that is on the second page, the UIVeiw is there to ACCEPT the demands of the UIViewController, not the other way around, you send commands to the UIView with the custom UITableView and the custom UITableView's data is set by the UIViewController. This is the correct way to do things. There's much much more that could be added to this example to make this super robust, but the idea is this:

Example 1: UIViewController1

button1 ==> UIViewController pulls information from network or tells the UIView to pull from network this information is stored in the custom UIButton that you make that holds an ARRAY of data

button2 ==> UIViewController pulls information from network or tells the UIView to pull from network this information is stored in the custom UIButton that you make that holds an ARRAY of data

button3 ==> UIViewController pulls information from network or tells the UIView to pull from network this information is stored in the custom UIButton that you make that holds an ARRAY of data

UIViewcontroller2 UITableView the data for each button that you stored in the array above is passed to the secondViewController and then populates the table, this is how it works.

Example 2: UIViewController1

button1 ==> UIViewController just adds an action event to the UIButton

button2 ==> UIViewController just adds an action event to the UIButton

button3 ==> UIViewController just adds an action event to the UIButton

UIViewcontroller2 TableView in a custom method calls the network and populates an array of data based on the title of the Buttons in UIViewcontroller1, this array is smashed into the second view controller by using a custom method that is called from your viewdidload in the second view controller, then, this second viewcontroller calls to its own view which has been typecast to the custom UIView subclass with a UITableView. This UIViewController2 then calls to "cellForRowAtIndexPath" and populates the table with setter methods on the cell, thereby forcing data into the UITableView

IN both methods, how many delegates did we set up in a custom view? ZERO, because the UIViewControllers should be in charge of setting the data and sending it downward, a UIView should never send data upward unless specifically asked to by the UIViewController:

Working simple example (programmatic, no storyboards, so you can see what's going on):

AppDelegate.swift:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var rootViewController: UINavigationController?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        window = UIWindow(frame: UIScreen.mainScreen().bounds)
        rootViewController = UINavigationController(rootViewController: ViewController())
        if let window = window {
            window.backgroundColor = UIColor.whiteColor()
            window.rootViewController = rootViewController
            window.makeKeyAndVisible()
        }
        return true
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var tableView : UITableView?
    var items = [
        "Ginger",
        "larry",
        "Moe",
        "Fred",
        "Terry"]

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView(frame: CGRectMake(0, 0, 414, 736), style: UITableViewStyle.Plain)
        tableView!.delegate = self
        tableView!.dataSource = self
        tableView!.registerClass(CustomTableViewCell.self, forCellReuseIdentifier: "Cell")
        self.view .addSubview(tableView!)
    }
    override func loadView() {
        var stuf = UIView()
        stuf.frame = CGRectMake(0, 0, 414, 736)
        stuf.backgroundColor = UIColor .redColor()
        self.view = stuf
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count;
    }
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat {
        return 44
    }   
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomTableViewCell
        cell.doWork = {
            () -> Void in
            self.doStuff(indexPath.row)
        }
        cell.labelMessage.text = items[indexPath.row] as String
        return cell
    }
    func doStuff(integer: NSInteger) {
        var newViewController = SecondViewController()
        newViewController.title = items[integer] as String
        var dismissButton = UIBarButtonItem(title: "done", style: UIBarButtonItemStyle.Plain, target: self, action: "dismiss")
        newViewController.navigationItem.rightBarButtonItem = dismissButton
        var navControl =  UINavigationController(rootViewController: newViewController);
        self.navigationController?.presentViewController(navControl, animated: true, completion: { () -> Void in })
    }
    func dismiss() {
        self.navigationController?.dismissViewControllerAnimated(true, completion: { () -> Void in})
    }
}

SecondViewController.swift

import UIKit

class SecondViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {

    var items = [
        "sports score1",
        "sports score2",
        "sports score3",
        "sports score4",
        "sports score5"]

    var myView: CustomViewWithTableView! {
        return self.view as! CustomViewWithTableView
    }
    override func loadView() {
        let height = self.navigationController!.navigationBar.frame.size.height as CGFloat

        view = CustomViewWithTableView(frame: CGRectMake(0, height, UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height-height as CGFloat))
    }
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        self.myView.tableView.delegate = self
        self.myView.tableView!.dataSource = self
        self.myView.tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count;
    }
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat {
        return 44
    }
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
        cell.textLabel!.text =  items[indexPath.row] as String
        return cell
    }
}

CustomViewWithTableView.swift

import Foundation
import UIKit

class CustomViewWithTableView: UIView {

    var tableView: UITableView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        tableView = UITableView(frame: CGRectMake(frame.origin.x, frame.origin.y, frame.width, frame.height), style: .Plain)
        self.addSubview(tableView)
    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

CustomTableViewCell.swift

import Foundation
import UIKit

class CustomTableViewCell: UITableViewCell, UIGestureRecognizerDelegate {

    var labelMessage = UILabel()

    var doWork: (() -> Void)?

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        let touchie: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "this")
        touchie.delegate = self
        touchie.cancelsTouchesInView = false
        self.addGestureRecognizer(touchie)
        labelMessage.setTranslatesAutoresizingMaskIntoConstraints(false)
        contentView.addSubview(labelMessage)

        //custom layout constraints
        var viewsDict =  ["labelMessage" : labelMessage]

        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[labelMessage]|", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))
        contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[labelMessage]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDict))

    }
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

   //custom touch interceptor for presentation navigation
    func this () {
        if let callback = self.doWork {
            println("swipe detected, cell function run")
            callback ()
        }
    }

}

As I stated above, the UITableView that sits in the custom UIView is dead until the UIViewController tells the view that it's not dead. This is how MVC should work, and in fact, to add on to this, there's a few things that I would add personally to make the point even more pronounced.

1. Another Custom UIView for the ViewController.swift

2. Another Custom UITableViewCell for the SecondViewController.swift

3. An object Model for the first set of Button data, this is an NSObject subclass, this is the "button" data on the first viewController's screen

4. Another object model (perhaps) to model the second set of data in the "scores" data

5. 2 custom subclasses of UILabel

6. 2 custom subclasses of UIButton

The UIView subclasses declare AS properties the subclassed UIElements in 5 and 6 above, so 5 and 6 control just themself and the UIViews are sort of like their immediate parent as a property of the UIView itself.

At the end of this project, I would have, for each ViewController around 5-10 subclasses with swift and possibly more (with ObjC it would be double this size). The parent class of the subViews only control the content of their subViews and properties, and NEVER control the content of the Parent view, this is how IOS was designed, encapsulation with the flow of "data chatter" extending downward from the UIViewController and not the other way around.

Anyway, good luck in your Swift adventures, I'm also learning Swift as I answer questions like this and translate intense and advanced ObjC code to Swift. Should you have additional questions, then please ask. Thanks and good luck!

Share:
15,275
B. Ceylan
Author by

B. Ceylan

Updated on June 04, 2022

Comments

  • B. Ceylan
    B. Ceylan almost 2 years

    I'm trying to implement a table view inside a UIView. One problem I'm currently struggling with is linking delegate and dataSource. If I uncomment two lines below Xcode gives an "expected declaration" error. If I link them from document outline Xcode says okay but then simulation crashes. It would be great if someone can figure out the problem.

    Thanks!

    import UIKit
    class CurrentGameDetailView: UIView, UITableViewDelegate, UITableViewDataSource {
    
        @IBOutlet weak var tableView: UITableView!
        var theGame: GameObject? = nil
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return theGame!.roundNumber
        }
    
    //    tableView.delegate = self
    //    tableView.dataSource = self
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("OldGameDetailCell", forIndexPath: indexPath) as! OldGameDetailCell
    
            cell.roundLabel.text = theGame!.gameRounds[indexPath.row].gamePlayed.description
            ...
    
            return cell
        }
    }