Reputation: 6051
I am trying to get cells' string
using a button in a custom UITableViewCell
.But when I tap the button app crashes due to this error :
fatal error: unexpectedly found nil while unwrapping an Optional value
.
I tried print
something and it works!.but when I try to get other cells' string app crashes. Here is my code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cellID:String!
var cell = OrderCell()
switch indexPath.section {
.
.
.
.
.
case 5 :
if indexPath.row == 0 {cellID = "Submit" }
cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! OrderCell
cell.submitButton.addTarget(self, action: #selector(OrderViewController.submitNewOrder), for: .touchUpInside)
default:
break
}
return cell
}
func submitNewOrder() {
//Title
let index1:IndexPath = IndexPath(row: 0, section: 0)
let cell1: OrderCell = tableView.cellForRow(at: index1) as! OrderCell
print("order is \(cell1.orderTitle.text!)")
}
I am sure that row
and section
are right!. Also I tired same method with @IBAction
and it works fine ! What is the problem ?
Thank you
Upvotes: 0
Views: 96
Reputation: 20369
All that you need is,
self.tableView(tableView, cellForRowAt: index1)
Rather than
tableView.cellForRow(at: index1) as! OrderCell
How it works ??
In tableview, cell gets reused. Your code will crash only if you scroll your tableview and later tap on button in some cell at bottom, reason cell at index path (0,0) is reused so tableView will not keep the cell at index path (0,0) hence when you call self.tableView(tableView, cellForRowAt: index1)
it returns nil.
On the other hand what you should have probably done is to ask the data source delegates to return the cell rather than the tableView itself. In this case, your own VC is data source, hence I wrote
self.tableView(tableView, cellForRowAt: index1)
This will get the reference to cell at index path (0,0) even if its not there with tableView and hand it over to u rather than nil :)
Suggestion :
Though the above code works absolutely fine, I personally recommend having IBAction from button to cell, As cell holds the button, it makes sense logically to create a IBAction of button in cell rather than in tableViewController.
You can always declare a delegate in your cell and implement it in your TableViewController to inform the button tap and letting know which button was tapped.
This way code will be clean to anybody who will look at it even once. Cell is responsible for handling IBAction of its subview and informs only tableViewController what to do on tapping button. TableViewController on the other hand just performs the task and never worry about which button on which it is tapped.
EDIT:
let index1:IndexPath = IndexPath(row: 0, section: 0)
let cell1 = self.tableView(tableView, cellForRowAt: index1) as! OrderCell
I dint change anything you can use your existing code as shown above :) Simply replace cellForRowAt :)
EDIT 2 :
Though the above provided code works absolutely fine, In a chat that we had (You can refer the chat in comment section) I realised that the cell at indexPath (0,0) has a textFiled.
Above solution though works fine for label, on using it with TextField it returned the textField.text as "".
This was obviously because of mistake in cellForRowAtIndexPath and because of cell reuse strategy of UITableView.
Though I have suggested formal and more proper suggestion in suggestion section of answer above, I believe because the answer is accepted, its my duty to provide fully functional code which will work with textField as well :)
Simplest thing to do would be update the data source once user enters some text in the textField of cell at indexPth (0,0) and update the textField text properly in cellForRowAt index path of tableView.
Doing it properly will no longer return textField text as "" on scrolling down or on cell getting reused.
Here is how I did it :)
Step 1:
Make your cell a textField delegate and implement textFieldDidEndEditing :)
class MyTableViewCell: UITableViewCell,UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
//will see implementation later
}
}
Step 2 :
Declare a protocol in your custom cell to inform the tableViewController to update its data source with user text :)
protocol updateDataSource {
func updateDataSource(with userText : String)
}
Step 3 :
Create a delegate in cell :)
var delegate : updateDataSource? = nil
Step 4 :
Implement textFieldDidEndEditing,
func textFieldDidEndEditing(_ textField: UITextField) {
self.delegate?.updateDataSource(with: textField.text!)
}
Step 5 :
Confirm delegate in your ViewController (TableViewController)
extension ViewController : updateDataSource {
func updateDataSource(with userText: String) {
//update your data source
//I dont have any hence am updating a instance variable in my VC
self.userEnterredText = userText
}
}
Finally update your CellForRowAtIndexPath as
cell.delegate = self
if indexPath.row == 0 && self.userEnterredText != nil {
cell.testTextField.text = self.userEnterredText!
}
else {
cell.testTextField.text = ""
}
return cell
This will make sure that when you call self.tableView(tableView, cellForRowAt: index1)
on a data source for a cell which is reused, to call cell for row at index path, and because of your code in cellForRowAt now your textfield will be populated properly :)
Now you can still use your code
let index1:IndexPath = IndexPath(row: 0, section: 0)
let cell1: OrderCell = tableView.cellForRow(at: index1) as! OrderCell
print("order is \(cell1.orderTitle.text!)")
With textField as well :)
Hope it helps :)
Upvotes: 2