Custom uitableview cell not showing all text labels - ios

I am trying to show an object from firebase in a custom cell. It has three text labels. When, I run my code it only shows one line of text per cell, instead of three, in the tableview. It only returns which ever text label is first.
Here is my code for my Class object:
class Class: NSObject {
var date_clasname: String?
var teacher: String?
var room_number: String?
init(dictionary: [String: Any]) {
self.date_clasname = dictionary["date_clasname"] as? String ?? ""
self.teacher = dictionary["teacher"] as? String ?? ""
self.room_number = dictionary["room_number"] as? String ?? ""
}
}
Here is my code for my tableview class:
class classes_test_TableViewController: UITableViewController {
let cellId = "cellId"
var users = [Class]()
override func viewDidLoad() {
super.viewDidLoad()
//navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
fetchClass()
}
func fetchClass() {
// guard let uid = ?.user.uid
// else{return}
//let userID = Auth.auth().currentUser!.uid
Database.database().reference().child("Science").observe(.childAdded, with: { (snapshot) in
//print(userID)
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = Class(dictionary: dictionary)
self.users.append(user)
print(snapshot)
//this will crash because of background thread, so lets use dispatch_async to fix
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
#objc func handleCancel() {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let use a hack for now, we actually need to dequeue our cells for memory efficiency
// let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let Class = users[indexPath.row]
cell.textLabel?.text = Class.date_clasname
cell.textLabel?.text = Class.teacher
cell.textLabel?.text = Class.room_number
return cell
}
}
class UserCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier:
String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is my database structure:
"Science" : {
"-Lgxm6qJhzI2IIG4uary" : {
"date_clasname" : "f",
"room_number" : "d",
"teacher" : "f"
}
The cell is suppose to show all three strings but only shows one.

You are using the standard UITableViewCell and you assign all three values to the same label.
You have to cast the cell to the custom cell and assign the values to the custom labels
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let use a hack for now, we actually need to dequeue our cells for memory efficiency
// let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let user = users[indexPath.row]
cell.nameLabel?.text = user.date_clasname
cell.teacherLabel?.text = user.teacher
cell.roomLabel?.text = user.room_number
return cell
}
Replace nameLabel, teacherLabel and roomLabel with the real property names.
And please conform to the naming convention and name variables lowerCamelCased for example dateClasname and roomNumber

Related

I need index path for both section and row in tableview, I got a index for row but not for the section

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCellrow", for: indexPath) as! TableViewCellrow
cell.imgView.image = UIImage(named: namearr[indexPath.section].1[indexPath.row])
cell.lblName.text = namearr[indexPath.section].1[indexPath.row]
cell.btnCross.tag = indexPath.row
cell.btnCross.addTarget(self, action: #selector(cutt), for: .touchUpInside)
If I got it right, you wanna store both row and section in button tag so it can be accessed in cutt function.
You can do it like this:
cell.btnCross.tag = (indexPath.section << 16) | indexPath.row
And then retrieve it in cutt func like this:
let section = button.tag >> 16
let row = button.tag & ((1 << 16) - 1)
Also note, that storing data in UIView tag is not the best solution in terms of performance.
UIKit implements tags using objc_get/setAssociatedObject(), meaning that every time you set or get a tag, you’re doing a dictionary lookup. Check out more here
An other option, which is I'd say is better in terms of architecture too, is moving touch handling into the Cell, like this:
class TableViewCellrow: UITableViewCell {
var btnCrossTapHandler: (() -> Void)?
#IBOutlet weak var btnCross: UIButton!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initialize()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initialize()
}
private func initialize() {
btnCross.addTarget(self, action: #selector(cutt), for: .touchUpInside)
}
#objc func cutt() {
btnCrossTapHandler?()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
cell.btnCrossTapHandler = { [weak self] in
// you have indexPath here
}
...
}
Don't use tuples in a datasource array. This is very bad practice.
Create a custom struct like
struct Section {
let title : String
let items : [String]
}
let namearr = [Section(title: "Vegtable", items: ["cauliflower","radish"]),
Section(title: "Fruit", items: ["grapres","mango"]),
Section(title: "FLower", items: ["lily","marigold"])]
The capitalized names can be simply created with
let section = namearr[indexPath.section]
let name = section.items[indexPath.row]
let capName = name.capitalized
For the tag I recommend a calculation section * 100 + row
func tag(for indexPath : IndexPath) -> Int {
return indexPath.section * 100 + indexPath.row
}
and the other way round
func indexPath(for tag : Int) -> IndexPath {
return IndexPath(row: tag % 100, section: tag / 100)
}

Im getting an error at cellForRowAt and im not sure why

I am getting an error in the function cellForRowAt
Cannot assign value of type 'Product' to type 'String?'
Is cell.textLabel?.text = product the issue that's causing this error?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items[indexPath.row]
cell.textLabel?.text = product
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
return cell
}
cell.textLabel?.text can only show a String object, not other objects.
It will be product.item or product.price or product.salesPrice or all in one line. (based on your requirement).
Make sure the value of product is not nil.
cell.textLabel?.text = "\(product.item) \(product.price) \(product.salesPrice)"
The full code you can try this:
class ViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var items:[Product]? = []
// VIEW LOAD
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
// ADD ITEMS
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField { (itemTextField) in
itemTextField.placeholder = "Item"
}
alert.addTextField { (priceTextField) in
priceTextField.placeholder = "Price"
}
alert.addTextField { (salePriceTextField) in
salePriceTextField.placeholder = "Sale Price"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
}
alert.addAction(action)
present(alert, animated: true)
storeData()
}
func addProduct(_ product: Product) {
let index = 0
items?.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
//STORE DATA
func storeData() {
UserDefaultUtil.saveData(products: items)
}
func getData() {
items = UserDefaultUtil.loadProducts()
}
}
//EXTENSION
extension ViewController: UITableViewDataSource {
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 = UITableViewCell()
let product = items![indexPath.row]
cell.textLabel?.text = "\(product.item!) \(product.price) \(product.salesPrice)"
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items?.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
storeData()
}
}
class UserDefaultUtil {
private static let Key = "savedData"
private static func archivePeople(people : [Product]) -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
}
static func loadProducts() -> [Product]? {
if let unarchivedObject = UserDefaults.standard.object(forKey: Key) as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [Product]
}
return nil
}
static func saveData(products : [Product]?) {
let archivedObject = archivePeople(people: products!)
UserDefaults.standard.set(archivedObject, forKey: Key)
UserDefaults.standard.synchronize()
}
}
class Product: NSObject, NSCoding {
var item: String?
var price: String?
var salesPrice: String?
required init(item:String, price:String, salesPrice: String) {
self.item = item
self.price = price
self.salesPrice = salesPrice
}
required init(coder aDecoder: NSCoder) {
self.item = aDecoder.decodeObject(forKey: "item") as? String
self.price = aDecoder.decodeObject(forKey: "price") as? String
self.salesPrice = aDecoder.decodeObject(forKey: "salesPrice") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "item")
aCoder.encode(price, forKey: "price")
aCoder.encode(salesPrice, forKey: "salesPrice")
}
}
cell.textLabel?.text = product
try doing product.item or product.price or product.salesPrice.
cell.textLabel?.text is expecting a string what you are setting is another type.
First of all reuse cells.
Never create cells with the default initializer.
Assign an identifier to the cell in Interface Builder (for example MainCell), then replace
let cell = UITableViewCell()
with
let cell = tableView.dequeueReusableCell(withIdentifier: "MainCell", for: indexPath)
The error is very clear. As already mentioned in other answers you have to assign the value of a property of Product to the label
cell.textLabel?.text = product.title
Don't create cells with default initializer
Assign an identifier to the tableview's cell in interface builder
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
you have to assign the property of Product to label's text
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let product = items[indexPath.row]
cell.textLabel?.text = product.price // product.item, product.salesPrice
return cell
}

Scrolling tableview causes checkmark to disappear | addressing reusable cells in Swift 5

I use the following to mark rows in a tableview as either marked with a checkmark or unselected with no checkmark. The issue that I have stumbled on is when scrolling the tableView seems to reload and cause the checkmark to disappear.
I understand this is caused by reusable cells,
Is there an easy fix I can implement into the code below?
class CheckableTableViewCell: UITableViewCell {
var handler: ((Bool)->())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
handler?(selected)
}
}
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self](selected) in
selected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let stringText = "\(item.code)"
cell.textLabel!.text = stringText
return cell
}
UPDATE:
struct Section {
let name : String
var items : [Portfolios]
}
struct Portfolios: Decodable {
let code: String
var isSelected: Bool
enum CodingKeys : String, CodingKey {
case code
}
}
You need to create your data model having a property called isSelected: Bool then use this to decide when a row should be selected or not. Note that you have to toggle this property every time didSelectRowAt(indexPath:) is triggered.
Example
// Declare your model with one of the property called isSelected
class MyModel {
var isSelected: Bool
init(isSelected: Bool) {
self.isSelected = isSelected
}
}
class TableViewController: UITableViewController {
// Dummy data note that it contains array of "MyModel" which have the "isSelected" property.
private var myModels: [MyModel] = [MyModel(isSelected: false),MyModel(isSelected: true),MyModel(isSelected: true),MyModel(isSelected: false) ]
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self](selected) in
/// Note that I am using selected Property here
myModels[indexPath.row].isSelected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let stringText = "\(item.code)"
cell.textLabel!.text = stringText
return cell
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedIndexPath = indexPath
myModels[selectedIndexPath.row].isSelected = true
self.tableView.reloadRows(at: [selectedIndexPath], with: .none)
}
}

How do I load Static UITableView Headers into my app from Firebase Firestore?

I have a firebase firestore database set up to store quiz data for my iOS app. My quiz data is supposed to load into a grouped static table view, with the section header being the question and each cell in the section being an answer choice. My problem is I can not figure out how to load data into the header.
https://imgur.com/a/SpZhtUL
This is what my quiz table view looks like right now. The data loaded in is actually supposed to be where "Header" is.
var quizArray = [Quiz]()
var db: Firestore!
let cellId = "cellId"
let headerId = "headerId"
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Question"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
tableView.register(AnswerCell.self, forCellReuseIdentifier: cellId)
tableView.register(QuestionHeader.self, forHeaderFooterViewReuseIdentifier: headerId)
tableView.sectionHeaderHeight = 50
tableView.tableFooterView = UIView()
db = Firestore.firestore()
loadData()
}
func loadData() {
db.collection("Quizzes").getDocuments() { (snapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let header = data["Header"] as? String ?? ""
let optionA = data["OptionA"] as? String ?? ""
let optionB = data["OptionB"] as? String ?? ""
let optionC = data["OptionC"] as? String ?? ""
let optionD = data["OptionD"] as? String ?? ""
let correctAnswer = data["CorrectAnswer"] as? String ?? ""
let newQuiz = Quiz(Header: header, OptionA: optionA, OptionB: optionB, OptionC: optionC, OptionD: optionD, CorrectAnswer: correctAnswer)
self.quizArray.append(newQuiz)
}
self.tableView.reloadData()
}
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return quizArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let quiz = quizArray[indexPath.row]
cell.textLabel?.text = "\(quiz.Header): \(quiz.OptionA): \(quiz.OptionB): \(quiz.OptionC): \(quiz.OptionD): \(quiz.CorrectAnswer)"
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let controller = ResultsViewController()
navigationController?.pushViewController(controller, animated: true)
}
}
Is it even possible to load in data for the static table view header or should I be looking into another way to format my quiz? Thanks for any help.
declare your header xib with this line
let questionHeaderView = questionHeader().loadNib() as! questionHeader
declare an extension for UIView
extension UIView {
/** Loads instance from nib with the same name. */
func loadNib() -> UIView {
let bundle = Bundle(for: type(of: self))
let nibName = type(of: self).description().components(separatedBy: ".").last!
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as! UIView
}}
then in viewDidload() function set tableview header after set it attributes
questionHeaderView.textLabel.text = "your text here"
self.tableView.tableHeaderView = questionHeaderView

How to preserve user input in UITableViewCell before dequeue

I'm creating an application in which I need the users to fill out a number of inputs in a UITableViewCell, kinda like a form. When the user taps on the done button, I need to collect those inputs so I can run some calculations and output them on another view controller
Here is the method I used to collect those inputs:
func doneButtonTapped() {
var dict = [String: Any]()
for rows in 0...TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected).count {
let ip = IndexPath(row: rows, section: 0)
let cells = tableView.cellForRow(at: ip)
if let numericCell = cells as? NumericInputTableViewCell {
if let text = numericCell.userInputTextField.text {
dict[numericCell.numericTitleLabel.text!] = text
}
} else if let booleanCell = cells as? BooleanInputTableViewCell {
let booleanSelection = booleanCell.booleanToggleSwitch.isOn
dict[booleanCell.booleanTitleLabel.text!] = booleanSelection
}
}
let calculator = Calculator(userInputDictionary: dict, ceiling_type: node.ceilingSelected)
}
The problem I'm having is when the cell is out of view, the user's input is cleared from the memory. Here are two screenshots to illustrate my point:
As you can see, when all the cells appears, the done button managed to store all the inputs from the user, evidently from the console print. However, if the cells are out of view, the inputs from area/m2 are set to nil:
The solution that came to mind was I shouldn't use a dequeue-able cell as I do want the cell to be in memory when it is out-of-view, but many of the stackover community strong against this practice. How should I solve this problem? Thanks!
UPDATE
Code for cellForRow(at: IndexPath)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let node = node else {
return UITableViewCell()
}
let cellArray = TableViewCells.getTableViewCell(ceilingType: node.ceilingSelected, moduleType: node.moduleSelected)
switch cellArray[indexPath.row].cellType {
case .numericInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "numericCell", for: indexPath) as! NumericInputTableViewCell
cell.numericTitleLabel.text = cellArray[indexPath.row].title
return cell
case .booleanInput :
let cell = tableView.dequeueReusableCell(withIdentifier: "booleanCell", for: indexPath) as! BooleanInputTableViewCell
cell.booleanTitleLabel.text = cellArray[indexPath.row].title
return cell
}
}
}
My two custom cells
NumericInputTableViewCell
class NumericInputTableViewCell: UITableViewCell {
#IBOutlet weak var numericTitleLabel: UILabel!
#IBOutlet weak var userInputTextField: UITextField!
}
BooleanInputTableViewCell
class BooleanInputTableViewCell: UITableViewCell {
#IBOutlet weak var booleanTitleLabel: UILabel!
#IBOutlet weak var booleanToggleSwitch: UISwitch!
}
Any takers?
I agree with the other contributors. The cells should not be used for data storage. You should consider another approach (like the one HMHero suggests).
But, as your question was also about how to access a UITableViewCell before it is removed, there is a method in UITableViewDelegate that you can use for that:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// do something with the cell before it gets deallocated
}
This method tells the delegate that the specified cell was removed from the table. So it gives a last chance to do something with that cell before it disappears.
Because of table view reuses its cells, usually, it's not a good idea if your data depends on some components from the table view cell. Rather, it should be the other way around. Your table view data always drive it's table view cell's component even before any user input data is provided in your case.
Initial Data - your should already have somewhere in your code. I created my own from your provided code
let data = CellData()
data.title = "Troffer Light Fittin"
data.value = false
let data2 = CellData()
data2.title = "Length Drop"
data2.value = "0"
cellData.append(data)
cellData.append(data2)
Example
enum CellType {
case numericInput, booleanInput
}
class CellData {
var title: String?
var value: Any?
var cellType: CellType {
if let _ = value as? Bool {
return .booleanInput
} else {
return .numericInput
}
}
}
protocol DataCellDelegate: class {
func didChangeCellData(_ cell: UITableViewCell)
}
class DataTableViewCell: UITableViewCell {
var data: CellData?
weak var delegate: DataCellDelegate?
}
class NumericInputTableViewCell: DataTableViewCell {
let userInputTextField: UITextField = UITextField()
override var data: CellData? {
didSet {
textLabel?.text = data?.title
if let value = data?.value as? String {
userInputTextField.text = value
}
}
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
userInputTextField.addTarget(self, action: #selector(textDidChange(_:)), for: .editingChanged)
contentView.addSubview(userInputTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func textDidChange(_ textField: UITextField) {
//update data and let the delegate know data is updated
data?.value = textField.text
delegate?.didChangeCellData(self)
}
//Disregard this part
override func layoutSubviews() {
super.layoutSubviews()
textLabel?.frame.size.height = bounds.size.height / 2
userInputTextField.frame = CGRect(x: (textLabel?.frame.origin.x ?? 10), y: bounds.size.height / 2, width: bounds.size.width - (textLabel?.frame.origin.x ?? 10), height: bounds.size.height / 2)
}
}
class BooleanInputTableViewCell: DataTableViewCell {
override var data: CellData? {
didSet {
textLabel?.text = data?.title
if let value = data?.value as? Bool {
booleanToggleSwitch.isOn = value
}
}
}
let booleanToggleSwitch = UISwitch(frame: .zero)
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
booleanToggleSwitch.addTarget(self, action: #selector(toggled), for: .valueChanged)
booleanToggleSwitch.isOn = true
accessoryView = booleanToggleSwitch
accessoryType = .none
selectionStyle = .none
}
func toggled() {
//update data and let the delegate know data is updated
data?.value = booleanToggleSwitch.isOn
delegate?.didChangeCellData(self)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In View Controller, you should update your original data source so when you scroll the table view, the data source privide right infomation.
func didChangeCellData(_ cell: UITableViewCell) {
if let cell = cell as? DataTableViewCell {
for data in cellData {
if let title = data.title, let titlePassed = cell.data?.title, title == titlePassed {
data.value = cell.data?.value
}
}
}
for data in cellData {
print("\(data.title) \(data.value)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let data = cellData[indexPath.row]
let cell: DataTableViewCell
if data.cellType == .booleanInput {
cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(BooleanInputTableViewCell.self), for: indexPath) as! BooleanInputTableViewCell
} else {
cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(NumericInputTableViewCell.self), for: indexPath) as! NumericInputTableViewCell
}
cell.data = cellData[indexPath.row]
cell.delegate = self
return cell
}
In short, try to have a single data source for table view and use the delegate to pass the updated data in the cell back to the data source.
Please disregard anything that has to do with layout. I didn't use the storyboard to test your requirements.

Resources