1
votes

I have a separate class from a UIViewController set up as my UITableView delegate and my UITableViewDataSource.

I attempt to initialize the UITableViewDelegate class and then assign it to the UITableView.

Here's what's odd...

The methods numberOfSectionsInTableView and tableView(:numberOfRowsInSection) are called five times, while tableView(_:cellForRowAtIndexPath) is never called.

I have verified both numberOfSectionsInTableView and tableView(:numberOfRowsInSection) return values of at least one.

If I move the UITableViewDataSource and UITableViewDelegate methods to the ViewController, the code works correctly.

What is causing this behavior?

class MessagesViewController: UIViewController, ManagedObjectContextSettable {

    var managedObjectContext: NSManagedObjectContext!
    @IBOutlet var messagesTableView: UITableView!

    override func viewDidLoad() {
        setupMessagesTableView()
    }

    private func setupMessagesTableView() {
        let dataSource = MessagesTableViewDataSource(managedObjectContext: managedObjectContext, conversationList: fetchedObjects as! [Conversation])
        // Assume fetchedObjects is an array fetched from CoreData store. I have removed the code that defines it for the purpose of this example.
        self.messagesTableView.dataSource = dataSource
        self.messagesTableView.delegate = dataSource
    }

}

class MessagesTableViewDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {

    var managedObjectContext: NSManagedObjectContext!
    var conversationList: [Conversation]

    required init(managedObjectContext: NSManagedObjectContext, conversationList: [Conversation]) {
        self.managedObjectContext = managedObjectContext
        self.conversationList = conversationList
        let conversation = Conversation()
        self.conversationList.append(conversation)            
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.conversationList.count    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("CellID", forIndexPath: indexPath) as UITableViewCell!
        let conversation: Conversation = self.conversationList[indexPath.row]
        cell.textLabel!.text = conversation.name as? String
        return cell
    }

}
3
Make sure that self.conversationList.count is greater zero.Arun Ammannaya
Could it be that conversationList is being populated in a background thread and it is not filled at the time the tableView is being displayed, in which case it's count would be zero?Michael
I add an instance to conversationList in the init function of the DataSource class specifically to avoid that problem. I have tested the value of conversationList.count to make sure it's 1.Kory
Probably unrelated, but you should use dequeueReusableCellWithIdentifier:forIndexPath: (has forIndexPath) as it will never return nil. Your current code needs to handle cell=nil.Michael
Thanks for that, I've edited the above code with the fixKory

3 Answers

1
votes

The problem is that your instance of MessagesTableViewDataSource gets deallocated. The delegate and dataSource properties on UITableView are weak. You declare your datasource (MessagesTableViewDataSource) as a local variable inside your function and thus nothing holds a strong reference to the instance of MessagesTableViewDataSource.

To fix this, define an instance variable for dataSource and assign it in viewDidLoad.

Example Code:

class MessagesViewController: UIViewController, ManagedObjectContextSettable {

let dataSource: MessagesTableViewDataSource?
var managedObjectContext: NSManagedObjectContext!
@IBOutlet var messagesTableView: UITableView!

override func viewDidLoad() {
    setupMessagesTableView()
}

private func setupMessagesTableView() {
   dataSource = MessagesTableViewDataSource(managedObjectContext: managedObjectContext, conversationList: fetchedObjects as! [Conversation])
    // Assume fetchedObjects is an array fetched from CoreData store. I have removed the code that defines it for the purpose of this example.
    self.messagesTableView?.dataSource = dataSource
    self.messagesTableView?.delegate = dataSource
}

}

0
votes

Check the frame of your table view. If the height or width is 0, tableView:cellForRowAtIndexPath: will not be called.

0
votes

I have the same problem and I believe this to be a bug in UIKit.

I created a simple list data source and a small view Controller to test this and I can confirm that cellForRowAtIndexPath does not get called. 'numberOfRowsInSection' returns a value greater 0 and the tableView's frame is set correctly.

The same code works when put in the view controller. Maybe I'm missing something here, but I think this is a bug on Apple's side.

SimpleListDataSource.swift

import UIKit

class SimpleListDataSource : NSObject, UITableViewDataSource {

    var items: [String]
    var cellIdentifier: String

    typealias CellConfiguration = (UITableViewCell, String) -> ()
    var cellConfiguration: CellConfiguration

    init(items: [String], cellIdentifier: String, cellConfiguration: CellConfiguration) {
        self.items = items
        self.cellIdentifier = cellIdentifier
        self.cellConfiguration = cellConfiguration
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print("Number of rows: \(self.items.count)")
        return self.items.count
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        print(__FUNCTION__)
        let cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier, forIndexPath: indexPath)
        self.cellConfiguration(cell, self.items[indexPath.row])
        return cell
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let data = ["a", "b", "c"]
        let dataSource = SimpleListDataSource(items: data, cellIdentifier: "cell") { (cell, string) -> () in
            cell.textLabel?.text = string
        }

        self.tableView.dataSource = dataSource
        self.tableView.reloadData()
    }
}