I just tried to retrieve all my textfield values out of my TableView. It worked for 10 of 11 cases. I tried the following:
let journeyIDTextField = tableView
I think one issue is that you're force unwrapping journeyIDTextField.cellInputTextfield.text
when it equals nil
. One other potential issue I see is that your textfield text will get wiped when you scroll due to cell reuse. For properly using a textfield in a reused cell, see this question.
You never want to "get text from a cell". Cells are reused, so when you scroll the text field that was in Section: 0 Row: 0
may now be in Section: 10 Row: 0
.
Instead, assign a "callback closure" to your cell in cellForRowAt
. When the user edits the text field, have your cell "call back" to the controller to update the data source.
Here is a complete example, with your code slightly modified:
class InputTableViewCell: UITableViewCell {
// callback closure to tell the controller the text field was edited
var callback: ((String) ->())?
let cellInputTextfield: UITextField = {
let cellInputTextfield = UITextField()
cellInputTextfield.textColor = .black
cellInputTextfield.sizeToFit()
return cellInputTextfield
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
cellInputTextfield.frame = CGRect(x: 20, y: 0, width: self.frame.width, height: 60)
cellInputTextfield.font = UIFont.systemFont(ofSize: 15)
self.contentView.addSubview(cellInputTextfield)
// add a target func to call when the text field is edited
cellInputTextfield.addTarget(self, action: #selector(textFieldChanged(_:)), for: .editingChanged)
}
@objc func textFieldChanged(_ textField: UITextField) -> Void {
// end the edited text back to the controller
callback?(textField.text ?? "")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
enum JourneySection:Int, CaseIterable, CustomStringConvertible{
case Description
case ID
case Confirmation
case Destination
case DestinationDate
case DestinationTime
case Arrival
case ArrivalDate
case ArrivalTime
case PriceTotal
case Companions
var description: String {
switch self {
case .Description: return "Description"
case .ID: return "ID f.ex. Flight Number"
case .Confirmation: return "Confirmation No."
case .Destination: return "Destination"
case .DestinationDate: return "Destination Date, like DD-MM-YYYY"
case .DestinationTime: return "Destination Time, like hh-mm"
case .Arrival: return "Arrival"
case .ArrivalDate: return "Arrival Date, like DD-MM-YYYY"
case .ArrivalTime: return "Arrival Time, like hh-mm"
case .PriceTotal: return "Total Price"
case .Companions: return "No. Of Companions"
}
}
}
class JourneyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let testButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Check Data", for: [])
v.setTitleColor(.white, for: [])
v.backgroundColor = .red
return v
}()
let tableView: UITableView = {
let v = UITableView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let reuseIdentifierInputCell = "journeyCell"
// declare a string data array
var dataStrings: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// initialize the data array with an empty string for each case
// in actual use, you may have already populated "saved" data
dataStrings = Array(repeating: "", count: JourneySection.allCases.count)
// add the button and table view
view.addSubview(testButton)
view.addSubview(tableView)
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain the button to Top: 20-pts and centered horizontally
testButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
testButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// constrain the tableview to 8-pts below the button
// Leading / Trailing at 20-pts
// with a height of 240 (so we can see what happens when scrolling)
tableView.topAnchor.constraint(equalTo: testButton.bottomAnchor, constant: 8.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
tableView.heightAnchor.constraint(equalToConstant: 240.0),
])
// register the cell class
tableView.register(InputTableViewCell.self, forCellReuseIdentifier: reuseIdentifierInputCell)
// set dataSource and delegate
tableView.dataSource = self
tableView.delegate = self
// dismiss keyboard when table scrolls
tableView.keyboardDismissMode = .onDrag
testButton.addTarget(self, action: #selector(showData(_:)), for: .touchUpInside)
}
@objc func showData(_ sender: UIButton) -> Void {
for i in 0..<JourneySection.allCases.count {
guard let section = JourneySection(rawValue: i) else {
fatalError("Something wrong with JourneySection")
}
print(section.description, ":", dataStrings[i])
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return JourneySection.allCases.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifierInputCell, for: indexPath) as! InputTableViewCell
guard let section = JourneySection(rawValue: indexPath.section) else { return UITableViewCell() }
// set placeholder
cell.cellInputTextfield.placeholder = section.description
// set the cell's text field's text
// if this entry in our data source is "", the placeholder will be shown
cell.cellInputTextfield.text = dataStrings[indexPath.section]
// we want the cell to "call us back" when the textfield is edited
cell.callback = { str in
// update our data source
self.dataStrings[indexPath.section] = str
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
When you run it, you should get:
You can enter text in the fields... scroll up and down... and when you tap the "Check Data" button you'll see your list of enum properties and the saved data from the fields.