Reputation: 25
I'm trying to wrap my head around delegates and protocols in swift, in particular how to pass data between custom tableview cells.
I've set up a simple project to test this out with three classes, a TableViewController and two custom cells using .xib for the layouts: DateLabelCell and DatePickerCell. The desired result being that when the datepicker is changed it updates the datelabel cell with the new value.
At the moment the DatePicker Cell is communicating with the TableViewController, but I cant seem to get the TableViewController to then communicate with the Datelabel Cell. I think it has something to do with the Datelabel Cell not referencing the tableviewcontroller correctly?
Any help or insights greatly appreciated.
Cheers,
TableViewController
import UIKit
protocol DateLabelDelegate: AnyObject {
func setDateLabel(_ text: String)
}
class TableViewController: UITableViewController {
weak var dateLabelDelegate: DateLabelDelegate?
override func viewDidLoad() {
super.viewDidLoad()
tableViewSetup()
}
func tableViewSetup() {
// Format tableView
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 200
tableView.tableFooterView = UIView(frame: CGRect.zero) // Removes Empty Cells
tableView.separatorStyle = UITableViewCell.SeparatorStyle.none
// Register Cells
tableView.register(UINib(nibName: "DatePickerCell", bundle: nil), forCellReuseIdentifier: "DatePickerCell")
tableView.register(UINib(nibName: "DateLabelCell", bundle: nil), forCellReuseIdentifier: "DateLabelCell")
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 2
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "DateLabelCell", for: indexPath) as! DateLabelCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "DatePickerCell", for: indexPath) as! DatePickerCell
cell.dateDelegate = self
return cell
}
}
}
extension TableViewController: DatePickerDelegate {
func setDate(_ text: String) {
print("WORKING: TableViewController: DatePickerDelegate")
dateLabelDelegate?.setDateLabel(text)
}
}
DatePickerCell
import UIKit
protocol DatePickerDelegate: AnyObject {
func setDate(_ text: String)
}
class DatePickerCell: UITableViewCell {
weak var dateDelegate: DatePickerDelegate?
@IBOutlet var datePicker: UIDatePicker!
let formatter = DateFormatter()
override func awakeFromNib() {
super.awakeFromNib()
formatter.dateFormat = "dd.MM.yyyy"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
}
@IBAction func datePickerDidChange(_ sender: Any) {
let result = formatter.string(from: datePicker.date)
print("RESULT -> \(result)")
dateDelegate?.setDate(result)
}
}
DateLabelCell
import UIKit
class DateLabelCell: UITableViewCell {
@IBOutlet var dateLabel: UILabel!
let date = Date()
let formatter = DateFormatter()
let sendingTableVC = TableViewController()
override func awakeFromNib() {
super.awakeFromNib()
sendingTableVC.dateLabelDelegate = self
formatter.dateFormat = "dd.MM.yyyy"
let result = formatter.string(from: date)
dateLabel.text = "Date: \(result)"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension DateLabelCell: DateLabelDelegate {
func setDateLabel(_ text: String) {
print("DATE LABEL UPDATED")
dateLabel.text = text
}
}
Upvotes: 0
Views: 1280
Reputation: 2038
You could also use callbacks, which are very powerful and somewhat easier to understand:
class TableViewController: UITableViewController {
//MARK: Properties
var date = "today"
.....
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "DateLabelCell", for: indexPath) as! DateLabelCell
cell.setDate(self.date)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "DatePickerCell", for: indexPath) as! DatePickerCell
cell.dateSelectedCallback = { newDate in //here we get the new date
self.date = newDate
self.tableView.reloadData()
}
return cell
}
}
}
DateLabelCell
class DateLabelCell: UITableViewCell {
//MARK: IBOutlets
@IBOutlet var dateLabel: UILabel!
//MARK: Cell setup
func setDate(_ text: String) {
dateLabel.text = text
}
}
DatePickerCell
Here we create an optional callback variable. Why optional? In case it is not being implemented, it won't crash. Callbacks are like simple completion handlers, and you can pass any data through them.
class DatePickerCell: UITableViewCell {
//MARK: IBOutlets
@IBOutlet var datePicker: UIDatePicker!
//MARK: Properties
var dateSelectedCallback: ((_ date: String) -> ())?
let formatter = DateFormatter()
//MARK: Lifecycles
override func awakeFromNib() {
super.awakeFromNib()
formatter.dateFormat = "dd.MM.yyyy"
}
//MARK: IBActions
@IBAction func datePickerDidChange(_ sender: Any) {
let result = formatter.string(from: datePicker.date)
print("RESULT -> \(result)")
dateSelectedCallback?(result) //we pass the date string here
}
}
Upvotes: 0
Reputation: 3886
The proper way of updating a tableview/collectionviewcell is to reload the particular cell. And you should always maintain a data represent your table view cells since your cells are not persistent and re-usable. You can use that data to populate the properties of your cell when it is created.
In your case, date is the data needs to be persisted. In this scenario, you could simply have that as a field in your controller.
import UIKit
class DateLabelCell: UITableViewCell {
@IBOutlet var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
func setDate(_ text: String) {
dateLabel.text = text
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
class TableViewController: UITableViewController {
var date: String = "Date"
.......
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "DateLabelCell", for: indexPath) as! DateLabelCell
cell.setDate(self.date)
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "DatePickerCell", for: indexPath) as! DatePickerCell
cell.dateDelegate = self
return cell
}
}
}
extension TableViewController: DatePickerDelegate {
func setDate(_ text: String) {
self.date = text
tableView.reloadRows(at: [IndexPath(item: 0, section: 0)], with: .automatic)
}
}
Upvotes: 1