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
}
}
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
}
}
Check the frame of your table view. If the height or width is 0, tableView:cellForRowAtIndexPath: will not be called.
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()
}
}
Related
I wrote a class that let me create multiple tableView simply. When I call this class for the first time, everything work well. But when I change some data, and reload the table, nothing changed.
Sample code:
class TestViewController: UIViewController {
var arrData = ["a","b","c"]
var myTableView: MyTableView?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView = MyTableView(table: tableView, data: arrData)
}
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.tableView.reloadData() //=> Not change anything
}
}
class MyTableView: NSObject, UITableViewDataSource {
var tableView: UITableView
var data: Array<String>
init(table: UITableView, data: Array<String>) {
self.data = data
self.tableView = table
super.init()
self.tableView.dataSource = self
self.tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "myCell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
cell.textLabel!.text = self.data[indexPath.row]
return cell
}
}
class MyTableViewCell : UITableViewCell {
//something here
}
When the view was loaded, the table has 3 rows: a,b,c. When I tap the button, nothing changed (expected: d,e,f)
Please help me!
Swift arrays are copied by value so the line self.data = data will take a copy of your array. Later changing the array contents of the source will not be reflected in the copy in your MyTableView.
You'll need to pass the array over again and take a second copy to update the table, e.g. write a method in MyTableView similar to the following:-
func setNewValues(data: Array<String>)
{
self.data = data
self.tableView.reloadData()
}
and call that from your buttonTapped function, i.e.:
#IBAction func buttonTapped(_ sender: UIButton) {
arrData = ["d","e","f"]
myTableView!.setNewValues(data: arrData)
}
Be careful with the force-unwrapped myTableView though - I'd replace that '!' with '?'.
When the user inserts an item into the database and then clicks the back button to return to the tableview, the tableview is not displaying the new item. I have to stop the app, and run it again for the new item to be displayed. My database is working perfectly fine, so i don't think thats the problem.
I do reload the database again in the mainController inside ViewDidLoad(). I also tried doing tableView.reloadData() inside ViewDidLoad() as well and that doesn't do anything.
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView?
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
// read from the database
mainDelegate.readDataFromDatabase()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainDelegate.people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BookTableViewCell") as! BookTableViewCell
let rowNum = indexPath.row
cell.lblTitle?.text = mainDelegate.people[rowNum].title
cell.accessoryType = .disclosureIndicator
return cell
}
class NewBookItemViewController: UIViewController, UITextFieldDelegate, UINavigationControllerDelegate {
#IBOutlet weak var titletxt : UITextField!
#IBAction func buttonSave(sender: UIButton){
// step 18b - instantiate Data object and add textfield data
let person : Data = Data.init()
person.initWithData(theRow: 0, thetitle: title)
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
// step 18c - do the insert into db
let returnCode : Bool = mainDelegate.insertIntoDatabase(person: person)
}
You need
override func viewWillAppear(_ animated:Bool) {
super.viewWillAppear(animated)
mainDelegate.readDataFromDatabase()
tableView.reloadData()
}
As viewDidLoad is called once when the vc is loaded , also it's not the responsiliblity of the appDelegate to do such things , conside having a dataSource class that handle read/write from the database
I can't figure out how to solve this. It keeps throwing an error with the dequeneResuableCell. I've tried every variable available. I have xcode 6. I removed the outlets, because they are not the problem and I have not included the constructor/iniitalization for the HospitalList class.
This is the code
import UIKit
class HospitalList: UIViewController {
#IBOutlet weak var hospitals: UITableView!
var Hos: [Hospital] = []
override func viewDidLoad() {
super.viewDidLoad()
Hos = createArray()
hospitals.delegate = self
hospitals.dataSource = self
}
func createArray() -> [Hospital] {
\\more code to create an array of Information
return tempHospitals
}
}
extension HospitalList: UITableViewDelegate, UITableViewDataSource {
//counts number of elements in Table View
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return Hos.count
}
//sets up whats in the table view
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let Hop = Hos[indexPath.row]
// this line is the problem right here
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoHospitals")
cell.HName.text = Hop.name \\and more, but not including
return cell
}
return UITableViewCell
}
I have a UITableView and my prototype cell consists of a label and a TextField. I also have a class MyClass that contains functions func1, func2, fun3, ... I have several ViewControllers that use the same tableViewCell prototype. Each viewController will have an instance of MyClass, called inst1, inst2, and inst3. When I enter text into FirstViewController's TableView I want each row to call a function from the instance of MyClass that corresponds to the row.
So when I enter text into row 1 on the FirstViewController I want to pass the data entered into the textField into func1 of inst1. When data is entered into row 2 of FirstViewController I want the data in the textfield to be passed into func2 of inst1. And so on and so forth down the rows.
I am very new to this and would really appreciate some help figuring out how to do this. Let me know if that doesn't make sense and I can try to rephrase it. I really need help with this. Thanks in advance!
*Updated question to show my code
Below is my Code:
FirstViewController.swift
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPath(for: cell) {
if (indexPath.hashValue == 0) {
inst1.func1(one: text)
}
if (indexPath.hashValue == 1) {
inst1.func2(two: text)
}
}
totalText.text = inst1.getMyTotal()
}
}
import UIKit
class FirstViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let inst1 = MyClass()
#IBOutlet weak var totalText: UILabel!
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 11
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell") as! TableViewCell
let text = cell.cellData[indexPath.row]
cell.myTextField.tag = indexPath.row
cell.delegate = self
cell.myLabel.text = text
cell.myTextField.placeholder = text
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
TableViewCell.swift
import UIKit
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class TableViewCell: UITableViewCell {
weak var delegate: MyCellDelegate?
public var cellData: [String] = ["1","2","3","4","5","6","7","8","9","10","11"]
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
When I set a breakpoint in the FirstViewController extension it never runs that code.
In WillDisplayCell add the tag to the UITextField. Also create a protocol to notify the Corrosponding viewController and set itself as the delegate here.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
cell.textField.tag = indexPath.row
cell.delegate = self
}
The protocol in your cell class will look something like this
protocol MyCellDelegate: class {
func MyCell(_ cell: UITableViewCell, didEnterText text: String)
}
class MyCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: MyCellDelegate?
override fun awakeFromNib() {
super.awakeFromNib()
textField.delegate = self
}
//All the remaining code goes here
func textFieldShouldReturn(_ textField: UITextField) -> Bool { //delegate method
textField.resignFirstResponder()
delegate?.MyCell(self, didEnterText: textField.text! )
return true
}
}
Now again in your FirstViewController which has conformed to be its delegate do this
extension FirstViewController: MyCellDelegate {
func MyCell(_ cell: UITableViewCell, didEnterText text: String) {
if let indexPath = tableView.indexPathForCell(cell) {
// call whichever method you want to call based on index path
}
}
How i can to create heir of UITableView class that will contain delegate and dataSource methods? I don't want to have dataSource and delegate methods in my ViewController.
You need to create another class for the purpose but first let's see how the ViewController will be.
As you can see the code is sort of self explanatory, I have created a custom class called TableViewDelegate that will be set as a delegate and dataSource of the tableView.
We are passing to TableViewDelegate, the data to be shown in the tableView, and function named didSelectRow that will be called by TableViewDelegate once a row is selected.
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
// data source
var data = [1, 2, 3, 4]
// delegate
var tableViewDelegate: TableViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// creating the delegate object and passing the data
tableViewDelegate = TableViewDelegate(data: data)
// passing a function to the delegate object
tableViewDelegate?.didSelectRow = didSelectRow
// setting the delegate object to tableView
tableView.delegate = tableViewDelegate
tableView.dataSource = tableViewDelegate
}
// a function that will be called by the delegate object
// when a row is selected
func didSelectRow(dataItem: Int, cell: UITableViewCell) {
let alert = UIAlertController(title: "Info", message: "\(dataItem) was selected.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(alert, animated: true, completion: nil)
}
}
The TableViewDelegate that is in charged of everything related of UITableViewDelegate, and UITableViewDataSource protocols.
class TableViewDelegate: NSObject, UITableViewDelegate, UITableViewDataSource {
var data = [Int]()
// variable that holds a stores a function
// which return Void but accept an Int and a UITableViewCell as arguments.
var didSelectRow: ((dataItem: Int, cell: UITableViewCell) -> Void)?
init(data: [Int]) {
self.data = data
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let text = String(data[indexPath.row])
cell.textLabel?.text = text
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)!
let dataItem = data[indexPath.row]
if let didSelectRow = didSelectRow {
// Calling didSelectRow that was set in ViewController.
didSelectRow(dataItem: dataItem, cell: cell)
}
}
}
Result:
I did this to avoid a long ViewController with a UIPickerViewDelegate/DS. You can simply make a class that conforms to UITableViewDelegate and UITableViewDataSource, instantiate this object in your view controller and assign it as the dataSource and delegate of the table view. For this class to send stuff back to your ViewController, you will have to make a protocol for the VC to conform to and give the class a delegate as well.
I read that this class must inherit from NSObject as the protocols are NSObject protocols, and it throws and error if they don't.
class MyCustomTableViewDel: NSObject, UITableViewDataSource, UITableViewDelegate {
weak var secondaryDelegate: TableViewSecondaryDelegate?
let rowData: [String]
init(dataForRows: [String]) {
rowData = dataForRows
super.init()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return rowData.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
secondaryDelegate?.doSomething(indexPath.row)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
....
return SomeCellForTheTableView
}
}
then make the secondary protocol:
protocol TableViewSecondaryDelegate {
func doSomething(row: Int)
}
then in your ViewController:
class myTableViewSceneController: UIViewController, TableViewSecondaryDelegate {
override func viewDidLoad() {
....
let tableViewDelAndDS = MyCustomTableViewDel(dataForRows: ["row0", "row1"])
tableViewDelAndDS.secondaryDelegate = self
tableView.delegate = tableViewDelAndDS
tableView.dataSource = tableViewDelAndDS
}
func doSomething(row: Int) { ... }
}