Dynamic datasource/delegates for UITableView in swift - swift

I need to set up different objects based on certain conditions as the datasource & delegate for table view.
But I am not able to assign tableview's datasource/delegate as it throws some errors.
Cannot assign a value of type NSObject? to a value of type UITableViewDelegate?
I did check this Q&A but this did not work.
var dataSourceDelegate:NSObject?
class RootViewController: UIViewController {
...
override func viewDidLoad() {
dataSourceDelegate = TableDataSourceDelegate()
// Table View
tableView = UITableView()
tableView!.setTranslatesAutoresizingMaskIntoConstraints(false)
tableView!.dataSource = dataSourceDelegate
// Cannot assign a value of type NSObject? to a value of type UITableViewDataSource?
tableView!.delegate = dataSourceDelegate
// Cannot assign a value of type NSObject? to a value of type UITableViewDelegate?
view.addSubview(tableView!)
// Constraints
var views:[String:UIView] = ["table":tableView!]
var hTableConstraint = "H:|[table]|"
var vConstraint = "V:|[table]|"
view.addConstraintsToView([hTableConstraint, vConstraint], view: view, viewVariables: views)
}
...
}
This is the datasource/delegate class
class TableDataSourceDelegate:NSObject, UITableViewDataSource, UITableViewDelegate {
// MARK: Datasource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return UITableViewCell()
}
// MARK: Delegates
}

NSObject? doesn't conforms to UITableViewDelegate, neither to UITableViewDataSource. You should create your protocol like
protocol GeneralDataSource: UITableViewDataSource, UITableViewDelegate {}
And then all data sources should conform that protocol.
class MyDataSource: NSObject, GeneralDataSource {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return UITableViewCell()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
Then you can use it like this
var myDataSource: GeneralDataSource?
override func viewDidLoad() {
super.viewDidLoad()
self.myDataSource = MyDataSource()
self.tableView.delegate = self.myDataSource
}

This is how your TableDataSourceDelegate should look like:
import UIKit
class TableDataSourceDelegate: NSObject {
}
extension TableDataSourceDelegate: UITableViewDataSource {
#objc func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
#objc func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "defaultCell")
cell.textLabel?.text = "test"
return cell
}
}
extension TableDataSourceDelegate: UITableViewDelegate {
// your delegate implementation here
}
And view controller implementation
import UIKit
// The typealias definition
typealias TVDataSourceDelegate = protocol<UITableViewDataSource, UITableViewDelegate>
class ViewController: UIViewController {
var dataSourceDelegate: TVDataSourceDelegate?
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
dataSourceDelegate = TableDataSourceDelegate()
// Table View
tableView = UITableView()
tableView!.translatesAutoresizingMaskIntoConstraints = false
tableView!.dataSource = dataSourceDelegate
tableView!.delegate = dataSourceDelegate
view.addSubview(tableView!)
// other code ...
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Although, I would recommend to separate dataSource and delegate objects (e.g. put the delegate protocol conforming code into your view controller's code.

Related

How do I call a different function for each TextField in a UITableView (Swift)?

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
}
}

Create custom UITableView class

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) { ... }
}

swift code error

import UIKit
class ViewController: UITableViewController {
var animals = [Animal]()
override func viewDidLoad() {
super.viewDidLoad()
self.animals = [Animal(name: "개"),Animal(name: "강아지"),Animal(name: "고양이"),Animal(name: "멍멍이"),Animal(name: "물어")]
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
var animal = Animal.self
animal = animals[indexPath.row] //1
cell.textLabel?.text = animal.name //2
return cell //3
}
}
I am getting the following errors:
error is cannot assign value of type 'Animal' to type 'Animal Type'
error is instance member 'name' cannot be used on type 'Animal
unexpected non-void return value in void function
In your code above, "didSelectRowAtIndexPath" is called after you select certain cell, and it does not return anything. Instead, if you want to display the cell, use "cellForRowAtIndexPath".
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController {
var animals = [Animal]()
override func viewDidLoad() {
super.viewDidLoad()
self.animals = [Animal(name: "개"),Animal(name: "강아지"),Animal(name: "고양이"),Animal(name: "멍멍이"),Animal(name: "물어")]
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
// This would works on your table view
var animal = animals[indexPath.row]
cell.textLabel?.text = animal.name
return cell
}
}

UITableViewDataSource not being invoked in Swift, Xcode 7 beta 2

This is a bizarre one, & I'm quite happy to be told that I've missed something obvious, but I can't see it.
Firstly, the UITableViewDataSource methods are not even showing up in autocomplete, then they're not called at runtime. Weirdly, the UITableViewDelegate methods are working on both counts.
Here's the (cut-down) code -
class PopupTable : NSObject, UITableViewDataSource, UITableViewDelegate
{
private var tableView : UITableView
init(rect: CGRect)
{
tableView = UITableView(frame: rect, style: UITableViewStyle.Plain)
super.init()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "PopupCell")
tableView.dataSource = self
tableView.delegate = self
}
func show()
{
if let mainWindow = UIApplication.sharedApplication().delegate?.window!
{
mainWindow.addSubview(tableView)
}
}
// MARK: TableView Delegate & DataSource
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return myData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("PopupCell")!
cell.detailTextLabel?.text = "some text"
return cell
}
}
I have no problems getting this to work in a UIViewController, but that's not what I want here.

Why can I not assign dataSource directly?

I would like to understand why to following Swift code does not work, but using the commented version does. I'm not sure if dataSources are typically wrapped into a separate class, but I don't think that should matter. I'm using Xcode 6.3.2, all up to date.
// MainViewController.swift
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataSource:UITableViewDataSource?
override func viewDidLoad() {
super.viewDidLoad()
// dataSource = MainTableViewDataSource()
// tableView.dataSource = dataSource
tableView.dataSource = MainTableViewDataSource()
}
}
The MainTableViewDataSource is just a class which implements the UITableViewDataSource protocol and uses some dummy data.
// MainTableViewDataSource.swift
import UIKit
class MainTableViewDataSource : NSObject, UITableViewDataSource {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 100
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1000
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return String(section + 1)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
cell.textLabel?.text = "Joejoe"
return cell
}
}
According to Apple's documentation https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instp/UITableView/dataSource
dataSource property of UITableView is unowned in Swift which is (assign) for Objective-C meaning that this property does not increase the reference count. So right after viewDidLoad function, when reference count of your MainTableViewDataSource becomes zero, it gets deallocated.
I recommend reading: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
You'll run into strange outcomes--sometimes even inconsistent--if you don't do the memory management right.