The problem is that UITableView.dataSource works fine with extensions, but does not work with delegates.
I created new project and added just one UITableView to the storyboard.
Here is the code using extension:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
// tableView.dataSource = Delegate()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension ViewController: UITableViewDataSource {
//class Delegate: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(1) // called several times
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(2) // called
var cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "UITableViewCell")
}
return cell!
}
}
Using delegate:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// tableView.dataSource = self
tableView.dataSource = Delegate()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
//extension ViewController: UITableViewDataSource {
class Delegate: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(1) // called several times
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(2) // doesn't called
var cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "UITableViewCell")
}
return cell!
}
}
Has anyone encountered such a problem?
You need to store your Delegate object in you view controller. The reason for this is that dataSource variable of UITableView is a weak variable (so as to prevent retain cycles) - it means that if it isn't stored somewhere in a strong variable, it will immediately get deallocated.
tableView.dataSource = Delegate()
Here you assign new instance of Delegate to a weak variable and nowhere else. Do something like this
class ViewController: UIViewController {
var delegate = Delegate()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self.delegate
}
The dataSource and delegate are both weak. So your Delegate() object with no strong reference will be released after viewDidLoad().
If you want to use delegate this way, just var delegate: Delegate! in your ViewController. Then in the viewDidLoad() :
self.delegate = Delegate()
tableView.delegate = self.delegate
Related
I have a UITableView on the ViewController. Here is complete code
import UIKit
class UnitTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var unitTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.unitTable.register(UnitCell.self, forCellReuseIdentifier: "UnitCell")
self.unitTable.delegate = self
self.unitTable.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = unitTable.dequeueReusableCell(withIdentifier: "UnitCell", for: indexPath) as! UnitCell
cell.unitNameLbl?.text = "TEST"
return cell
}
}
But unfortunately my custom cell is not visible. I've put green background colour to be sure that constraints are right.
Here is my UnitCell code
import UIKit
class UnitCell: UITableViewCell {
#IBOutlet weak var unitNameLbl: UILabel?
#IBOutlet weak var unitNumberLbl: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
What could be wrong if I can't see my custom cell?
try this following into viewDidLoad
unitTable.register(UINib(nibName: "yourCellClassName", bundle: nil), forCellReuseIdentifier: "UnitCell")
You can't directly use class name of cell because you'r using XIB of cell so you must need to register cell with nib name like below.
let nib = UINib(nibName: "YourTableViewCellNibName", bundle: nil)
self.unitTable.register(nib, forCellReuseIdentifier: "UnitCell")
change registering cell to table code as below.
override func viewDidLoad() {
super.viewDidLoad()
self.unitTable.register(UINib(nibName: "UnitTable", bundle: nil), forCellReuseIdentifier: "UnitCell") // nibName should be exact name of xib file of your custom cell.
self.unitTable.delegate = self
self.unitTable.dataSource = self
}
I want to show a button on my custom UITableViewCell which takes the user to another screen on tapping on it.
I have tried following code but it doesn't work
Child view:
#IBAction func childScreenButton(sender: AnyObject) {
if let delegate = self.delegate {
delegate.childButtonClickedOnCell(self)
}
}
Protocol:
protocol childTableCellDelegate: class {
func childButtonClickedOnCell(cell: childViewCell)
}
Parent ViewController:
func childButtonClickedOnCell(cell: FeedChildViewCell) {
self.clickedIndexPath = self.feedsTableView.indexPathForCell(cell)
self.performSegueWithIdentifier("toNextScreen", sender: self)
}
while I'm testing the break point doesn't enter into "delegate.childButtonClickedOnCell(self)" on child view. Please let me know if am doing anything wrong here. Thanks!!
I suspect you've got a couple things out of place, or not defined just right.
I just ran a quick test with this, and the delegate call works fine... see if you notice anything not-quite-the-same...
//
// TestTableViewController.swift
//
// Created by DonMag on 4/7/17.
// Copyright © 2017 DonMag. All rights reserved.
//
import UIKit
protocol MyCellDelegate {
func pressedButtonForMyCell(theSender: MyCell)
}
class MyCell: UITableViewCell {
#IBOutlet weak var theLabel: UILabel!
#IBOutlet weak var theButton: UIButton!
var delegate: MyCellDelegate?
#IBAction func childScreenButton(sender: AnyObject) {
delegate?.pressedButtonForMyCell(theSender: self)
}
}
class TestTableViewController: UITableViewController, MyCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyCell
cell.theLabel.text = "\(indexPath)"
cell.delegate = self
return cell
}
func pressedButtonForMyCell(theSender: MyCell) {
print("delegate called", theSender)
}
}
My ViewController is getting way too big because of all the methods needed for UITableViewDelegate and UITableViewDataSource, so I refactored my code to a different file, but now the data doesn't show up anymore...
Here's my current implementation:
In MyViewController.swift
class SomeView: UIViewController {
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myDataSourceClass(tableView: myTableView)
}
}
In MyNewDataSourceClass.swift
class myDataSourceClass: UITableViewDelegate, UITableViewDataSource {
let stuff = [1, 2, 3]
init(tableView: UITableView) {
super.init()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")
cell?.textLabel?.text = "please show up"
return cell!
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.stuff.count
}
}
Any help would be very appreciated. Thank you!
// you need to reload table after setting delegate and datasource
class SomeView: UIViewController {
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myDataSourceClass(tableView: myTableView)
myTableView.reloadData()
}
}
If I would like to have the same basic UITableView appearing in two different scenes, is it a good idea to use one datasource and delegate location for both tables?
I wanted to try this, but when I select the table view in IB and try to drag the line to a custom class of UITableView file, or even to another custom view controller, it will not connect. It only seems possible to make the current View Controller into the table's datasource and delegate(?).
I'm wondering if this is at least similar to this question, but even if it is, how is this done in swift (and perhaps there is a new way to do this).
Swift 4.1. You can create separate class and inherit it from UITableViewDataSource and UITableViewDelegate class. Here, I am implementing UITableViewDataSource() methods in DataSource class. You also need to confirm NSObject so that we don’t have to fiddle with the #objc and #class keywords because UITableViewDataSource is an Objective-C protocol.
import Foundation
import UIKit
class DataSource: NSObject, UITableViewDataSource {
var formData: [FormData]? = nil
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.formData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let label = cell?.contentView.viewWithTag(100) as? UILabel
let type = self.formData![indexPath.row]
label?.text = type.placeHolder
return cell!
}
}
Now, We will set DataSource to UITableView. If we crate separate class then we have to pass data to DataSource class.
class ViewController: UIViewController {
#IBOutlet weak var tblView: UITableView!
var formData: [FormData]? = nil
var dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
formData = FormData.array
dataSource.formData = formData // Pass data to DataSource class
tblView.dataSource = dataSource // Setting DataSource
}
}
In similar way you can implement UITableViewDelegate in separate class. The other way to separate DataSource and Delegate is by creating extension of your viewController. Even you can crate separate class where you can only define extensions for your view controller. In you define extension then you don't need to pass data.
class ViewController: UIViewController {
#IBOutlet weak var tblView: UITableView!
var formData: [FormData]? = nil
override func viewDidLoad() {
super.viewDidLoad()
formData = FormData.array
tblView.dataSource = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.formData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let label = cell?.contentView.viewWithTag(100) as? UILabel
let type = self.formData![indexPath.row]
label?.text = type.placeHolder
label?.backgroundColor = UIColor.gray
return cell!
}
}
Here is a code example showing different Datasource and delegates for UITableView.
Code in Swift
import UIKit
// MARK: Cell
class ItemCell: UITableViewCell{
var label: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 20))
label.textColor = .black
label.backgroundColor = .yellow
contentView.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: Main View Controller
class BlueViewController: UIViewController{
var tableView: UITableView!
var myDataSourse: MyTVDataSource!
var myDelegate: MyTVDelegate!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .blue
tableView = UITableView()
myDataSourse = MyTVDataSource(tableView: tableView)
myDelegate = MyTVDelegate()
myDelegate.presentingController = self
tableView.dataSource = myDataSourse
tableView.delegate = myDelegate
tableView.register(ItemCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(tableView)
self.tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
])
}
}
extension BlueViewController: BluePresenting{
func currentSelected(_ indexPath: IndexPath) {
print(indexPath)
}
}
// MARK: TableViewDelegate
protocol BluePresenting: class {
func currentSelected(_ indexPath: IndexPath)
}
class MyTVDelegate: NSObject,UITableViewDelegate{
var presentingController: BluePresenting?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
presentingController?.currentSelected(indexPath)
}
}
// MARK: TableView DataSource
class MyTVDataSource: NSObject, UITableViewDataSource{
private var tableView: UITableView
private var items = ["Item 1","item 2","item 3","Item 4"]
init(tableView: UITableView) {
self.tableView = tableView
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ItemCell(style: .default, reuseIdentifier: "Cell")
cell.label.text = items[indexPath.row]
return cell
}
}
You can implement a custom class object, and implement the UITableViewDataSource methods for this class.
#interface MyDataSource : NSObject <UITableViewDataSource>
//...
#end
And then, the UITableView has properties, delegate and dataSource.
Assign right objects to those properties.
MyDataSource ds = ... ///< Initialize the dataSource object.
self.tableView.dataSource = ds; ///< Let ds be the dataSource of `self.tableView`
self.tableView.delegate = .... ///< Assign the delegate, generally it is `self`.
Each Tableview should have its own Tableview controller. This is in accordance with the Model View Controller Design Pattern.
If the data in the the two tables are the same, you could have a common class serve as the dataSource.
I want to create a page which should have a map on the top and 5 row tableview at the bottom. So i created UIViewController put my Map View then put TableView. I created myViewController.swift and myTableViewCell.swift
But when i try on simulator no data showing on tableview. Only empty cells.I received no error
Here is my code. Thank you
import UIKit
import MapKit
class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var mapView: MKMapView!
var labels = ["some arrays"]
var images = ["some image names in an array"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return labels.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mySegue", forIndexPath: indexPath) as! myTableViewCell
cell.myCellLabel.text = labels[indexPath.row]
cell.myCellImage.image = UIImage(named: images[indexPath.row])
return cell
}
}
try this:
override func viewDidLoad() {
super.viewDidLoad()
// Add this
tableView.delegate = self
tableView.dataSource = self
}
You should do one out of two way :
tableView.delegate = self
tableView.dataSource = self
in the viewDidLoad() of your viewController
OR
- select the tableView and dragging by pressing control to viewController, first select the datasource and again doing so select the delegate.
Assign delegate and datasource of UITableView/ UICollectionView through storyboard to reduce code as follows:
Right click on your UITableView, then click on round circle for Delegate/ Datasource and move on to the viewcontroller. For clarity, see the image below.
Set the dataSource of the tableview.
tableview.dataSource = self
tableview.delegate = self