UITableViewCell tag changes after scrolling [duplicate] - ios

This question already has answers here:
Swift: retrieving text from a UITextField in a custom UITableViewCell and putting it in an array
(2 answers)
Closed 5 years ago.
I am creating a form in UITableView. This form has a UILabel and UITextField in a prototype cell. Each UITextField has a unique tag.
When user clicks submit button, I want to get value from each UITextField using a tag.
Now the problem is I have 18 UITextField and when I enter values in the field and scroll the view to fill the remaining fields, after filling the data when I press submit button, the app crashes. It won't be able to find the tag of first UITextField.
I want to compare UITextFields value. But I don't want to collect all textfields value. I want to retrieve particular textfield value and compare it with other textfield value on the submit button click.
This is how my UITableView Looks
Here is my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SurveyFormTableViewCell", for: indexPath) as! SurveyFormTableViewCell
var pest_code = textfield_id_value[indexPath.row].pest_code
var srno = textfield_id_value[indexPath.row].srno
var inspection_no = textfield_id_value[indexPath.row].inspection_no
cell.text_field.layer.cornerRadius = 3
cell.text_field.layer.borderWidth = 1
cell.text_field.addTarget(self, action: #selector(tableFieldDidChange(_textField:)), for: .editingChanged)
let id:Int = Int("\(srno)\(pest_code)\(inspection_no)")!
cell.text_field.tag = id
cell.label?.text = struc_array[indexPath.item].pest
cell.label?.numberOfLines = 0
cell.label?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.layer.borderWidth = 1
return cell
}
#IBAction func submit_survey_clicked(_ sender: UIButton) {
text_field = self.table_view.viewWithTag(textfield_tag1) as! UITextField
text_field2 = self.table_view.viewWithTag(textfield_tag2) as! UITextField
}

This isn't going to work for a couple of reasons:
1) The table view cells are reused. So you get the cell for the first item and assign it say tag 1 but then as you scroll that cell gets reused for say cell 15 (just an example) and you assign the tag the number 15. Now the SAME textfield that had the tag 1 has the tag 15.
2) You are looking on your table view for a view with the text field tag but the text field is not a sub view of the table view it belongs to the cell instead so it will never be found.
EDIT
This is a very basic example of using a data model Basic Data Model Example
(Note: this won't stay available for ever I may delete it in a week or so)
(Also Note: this is a very basic example and not in anyway the most complete method with proper validation, etc).
EDIT
If you want to make a list of the values that have been entered for each unique entry in your data list you can use a dictionary. This is an example based on the details you have supplied in the comments:
First change the textfieldId struct so that it adopts the Hashable protocol and can therefore be used as the key in your dictionary like this:
struct textfieldId: Hashable {
var srno:Int
var pest_code:Int
var inspection_no:Int
var hashValue: Int {
return srno.hashValue ^ pest_code.hashValue ^ inspection_no.hashValue
}
static func ==(lhs: textfieldId, rhs: textfieldId) -> Bool {
return lhs.srno == rhs.srno && lhs.pest_code == rhs.pest_code && lhs.inspection_no == rhs.inspection_no
}
}
Then define a dictionary to hold the details that have been entered for each of the data items like this:
var enteredValues: [textfieldId: String] = [:]
Now whenever the text entered changes you store it in the dictionary like this:
self.enteredValues[id] = textEntered // Update for your project
and to get the information back again (for example to update the cell) do this:
cell.textField.text = self.enteredValues[id] // Update for your project.
Doing this you have created a dictionary of the values that have been entered that is unique to each data item.
If you need to clear the list you can do this:
self.enteredValues.removeAll()
or create a new instance like this:
self.enteredValues = [:]

You must assign indexPath.row in cell forsizeitem. Like this
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SurveyFormTableViewCell", for: indexPath) as! SurveyFormTableViewCell
var pest_code = textfield_id_value[indexPath.row].pest_code
var srno = textfield_id_value[indexPath.row].srno
var inspection_no = textfield_id_value[indexPath.row].inspection_no
cell.text_field.layer.cornerRadius = 3
cell.text_field.layer.borderWidth = 1
cell.text_field.addTarget(self, action: #selector(tableFieldDidChange(_textField:)), for: .editingChanged)
let id:Int = Int("\(srno)\(pest_code)\(inspection_no)")!
cell.text_field.tag = indexPath.row
cell.label?.text = struc_array[indexPath.item].pest
cell.label?.numberOfLines = 0
cell.label?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.layer.borderWidth = 1
return cell
}

Related

Textfields getting cleared in tableview [duplicate]

This question already has answers here:
How to get the values of multiple textFields in UITableView with dynamic cells?
(2 answers)
Closed 3 years ago.
I am currently creating an application and atm I am at the login and sign up function. I decided to create a tableview where I've textfields so the user can sign up. But every time I enter something in the textfield and scrolls down, the textfields is getting cleared out, but why?
Also, how can I add this data to my database when I tap a button? Since I cannot reach it from there. Any input?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "signUpCell", for: indexPath) as! SignUpTableViewCell
cell.txtfield.delegate = self
cell.textOfField.text = textNames[indexPath.section][indexPath.row]
cell.txtfield.text = textFields[indexPath.section][indexPath.row]
#IBAction func signUpButtonTapped(_ sender: Any)
{
let authentication = Auth.auth().currentUser?.uid
//I know how to add it to the database, but how can I retrieve the tableview data here?
}
Cells are dequeud you need to update the data source array , this inside cellForRowAt
cell.textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
cell.textField.indexPath = indexPath
And
#objc func textFieldDidChange(_ textField: CustomTexf) {
let index = textField.indexPath
textFields[index.section][index.row] = textField.text!
}
Create this class and assign it to the textfield
class CustomTexf : UITextField {
var indexPath:IndexPath!
}

summing values on UITableView Cells swift

I have a TableView in a view controller and a UILabel in the UIViewController too. Everything gets displayed well but I am trying to achieve something and I cannot simply get the Logic behind it.
The tableview cell has a UILabel that has Int values on the cells, thiese numbers varies now how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller.
I have tried creating a variable in the UIView controller and adding this value to t but I am unable to do that because I cannot add this number together
any help as to how to do this. Thanks
var total: Double?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! BrokageCheckoutCell
cell.configure()
total = Double("\(cell.subPrice.text!)")
cell.selectionStyle = .none
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: cellId2, for: indexPath) as! OtherCheckoutCell
cell.configure()
cell.selectionStyle = .none
return cell
default: break
}
return UITableViewCell()
}
You ask:
how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller
In short, you don’t. The label in the table view cell (which is part of the “view”) is not our source of data. It’s only used to show the individual strings.
For example, let’s say your app has 100 items to be summed, but the app’s table view shows, say, only 12 rows at a time. As a memory and performance improvement, iOS will reuse cells that scroll out of view in order to show the contents for new rows that scroll into view. But this means that you can’t say “sum all of the contents of the labels in those 100 cells” because there aren’t 100 cells; there are only 12.
Your app should instead refer to its “model”, that structure (e.g., an array) that cellForRowAt used to determine what to show in a given table view cell. So, if you want to add up a grand total, you should add up the values in that array, not referring to the cells at all.
And if your cell has controls that allow data to be modified, then the cell should initiate the update of the model. Then, the process of calculating the grand total (summing the values in the array) still works.
So, let’s consider an example:
You should have a model that contains the numbers that you want to sum. Your example is a little unclear, so I’ll create an example where each table row contained a name, a unit price, and a quantity:
struct Item {
let name: String
let unitPrice: Decimal
var quantity: Decimal
}
In this example, the total for any given row will be the quantity times the unit price. And the grand total for all the rows will be the sum of all of those products.
Then your view controller might have an array of those for its model:
var items: [Item] = ...
Then when you want to update your grand total label, you’d just have a method that calculated the quantity times the price for the total for each row and then sum up those values to calculate the grand total. It will then update the grand total text field accordingly:
private extension ViewController {
func updateTotal() {
let total = items
.map { $0.quantity * $0.unitPrice }
.reduce(0, +)
totalLabel.text = totalFormatter.string(for: total)
}
}
Note, I am not retrieving these numbers from the cells (because those might be reused), but rather I’m retrieving all the data I need from the model.
The key here, though, is if your cell, for example, allowed the user to change one of those values, you need a mechanism to update the table view controller’s model. We’ll use a protocol for that:
protocol ItemCellDelegate: class {
func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal)
}
class ItemCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var quantityTextField: UITextField!
#IBOutlet weak var unitPriceLabel: UILabel!
static let quantityFormatter: NumberFormatter = ...
static let priceFormatter: NumberFormatter = ...
weak var delegate: ItemCellDelegate?
}
Obviously, when you configure the cell, the view controller will specify the delegate for updates to the text field (or whatever):
// MARK: Public methods
extension ItemCell {
func configure(name: String, quantity: Decimal, unitPrice: Decimal, delegate: ItemCellDelegate) {
self.delegate = delegate
nameLabel.text = name
quantityTextField.text = ItemCell.quantityFormatter.string(for: quantity)
unitPriceLabel.text = ItemCell.priceFormatter.string(for: unitPrice)
}
}
And when the ItemCell gets updates, it just calls the delegate:
// MARK: Private methods
private extension ItemCell {
#IBAction func didUpdateQuantity(_ sender: UITextField) {
var quantity: Decimal?
if let text = sender.text, let value = ItemCell.quantityFormatter.number(from: text) {
quantity = value.decimalValue
}
delegate?.cell(self, didUpdateQuantity: quantity ?? 0)
}
}
Then, when the view controller configured the cell, it would supply itself as the delegate:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
let item = items[indexPath.row]
cell.configure(name: item.name, quantity: item.quantity, unitPrice: item.unitPrice, delegate: self)
return cell
}
}
And, of course, the view controller would conform to that protocol to accept updates to the text field and update the model:
extension ViewController: ItemCellDelegate {
func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
items[indexPath.row].quantity = quantity
updateTotal()
}
}
And, as you can see, if can, after updating the model, it can automatically update the total, too.
The key here is that we never use the cells as a place where we hold data. The cells are just for (a) displaying data and (b) informing the view controller if there are any updates it needs to be aware of.
We strive for a very clear separation of responsibilities, where “views” are for showing and interacting with controls presently on screen and the “model” contains all of our data.
The usual way to do this is to have a delegate of the cell, and make your ViewController this delegate. Whenever the value of the cell changes, send this value through the delegate so that the view controller can add up all these values.
That's the quick way.
The better way to do this is to have a datasource for your cells that keeps track of the values. This is better, because as the cells scroll on and off the screen, the datasource can keep track of the values and restore them when that index path becomes visible again.
Since the data source knows what the values are, it's trivial to sum them.

Comparing an list of array and selected value and displaying it in tableview cell based on index

I have an application wherein when tableview cell is clicked, another tableview is loaded and an api call is made. Based on the response from api, table view list is loaded and when a particular item in the second tableview is selected, there is a selected checkbox displayed just besides the tableview text label and at the same time database is updated with selected value,
so when I come back to the first tableview I display a label with selected item.
When the first tableview cell I clicked, api is called and results of api should be compared with the active list from database and that particular cell should remain selected.
When there is some item selected in first tableview and when i click on that particular cell, api results reload the tableview and selection for respective cell is not displayed.
Following is the code:
for selectedDict in (appDelegate?.selectedCategoryFilterArray)! {
let selectedUuid = selectedDict.categoryUuid
print("selectedUuid\(selectedUuid)")
for allDict in self.requestedFiltersArray!{
let allUuid = allDict.objectForKey("uuid") as? String
if selectedUuid == allUuid {
cell.imgSelected.image = UIImage(named: "radio_selected")
continue
}else{
cell.imgSelected.image = UIImage(named: "radio")
}
print("allUuid\(allUuid)")
}
}
This is not working as expected, no cell is displaying as selected even if their is a cell selected.
Where have you placed this code?
From what I think you might be trying to achieve, here is an approach that I would suggest.
For your second tableView which is loaded based on the API call results, I would add a String property called uuid to your custom UITableViewCell class.
Then when you call cellForRowAtIndexPath to populate your second tableView, instantiate each cell as your custom UITableViewCell and set its uuid property to the appropriate value based on the results array, using the indexPath.row.
After setting that, next you can run the for loop with your condition to match the cell's uuid property value against the selectedUuid value from your AppDelegate and thereafter set the image as per the logic.
A rough implementation:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier") as! YourCustomTableViewCell
let allDict = self.requestedFiltersArray[indexPath.row]
let allUuid = allDict.objectForKey("uuid") as? String
cell.uuid = allUuid
for selectedDict in (appDelegate?.selectedCategoryFilterArray)! {
let selectedUuid = selectedDict.categoryUuid
if selectedUuid = cell.uuid {
cell.imageSelected.image = UIImage(named: "radio_selected")
} else {
cell.imageSelected.image = UIImage(named: "radio")
}
}
}

Swift Button inside CustomCell (TableView) passing arguments to targetMethod

My TableView features custom Cells which have a button to display corresponding detailed info in another view.
This thread here got me started and I tried to implement the approach with the delegate inside the customCell:
How to access the content of a custom cell in swift using button tag?
What I want to achieve is that when I click on the button it reads the name of the cell and passes it on to the next controller. However it seems that I cannot pass the name with the delegate method and its field is nil.
How can I get the specific content of a cell when clicking on its button?
This is what I did so far:
In the class creating my own cell I set delegate:
protocol CustomCellDelegate {
func cellButtonTapped(cell: DemoCell)
}
(........)
var delegate: CustomCellDelegate?
#IBAction func buttonTapped(sender: AnyObject) {
delegate?.cellButtonTapped(self)
}
In the TableViewController I have the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("FoldingCell",
forIndexPath: indexPath) as! DemoCell
cell.delegate = self
//TODO: set all custom cell properties here (retrieve JSON and set in cell), use indexPath.row as arraypointer
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
cell.schoolNameClosedCell.text = itemForThisRow["name"] as! String
cell.schoolNameOpenedCell.text = itemForThisRow["name"] as! String
self.schoolIdHelperField = itemForThisRow["name"] as! String
cell.schoolIntroText.text = itemForThisRow["name"] as! String
//call method when button inside cell is tapped
cell.innerCellButton.addTarget(self, action: #selector(MainTableViewController.cellButtonTapped(_:)), forControlEvents: .TouchUpInside)
cell.school_id = itemForThisRow["name"] as! String
// cell.schoolIntroText.text = "We from xx University..."
return cell
}
And finally the target method when the button inside the cell is clicked
func cellButtonTapped(cell: DemoCell) {
print("the school id: ")
print(cell.schoolNameOpenedCell) //this line throws an error EXC_BAD_ACCESS 0x0
}
Firstly, the object innerCellButton is not a Cell, it's a button. The simple way to solve your problem is, just refer the index of the button. Please find the below method.
func cellButtonTapped(AnyObject: sender) {
let resultList = self.items["result"] as! [[String: AnyObject]]
//Get the tag value of the selected button.
//Button tag should be matching with the corresponding cell's indexpath.row
let selectedIndex = sender.tag
let itemForThisRow = resultList[selectedIndex]
print("the school id: \(itemForThisRow[\"name\"])")
}
* And set each button's tag as indexPath.row *
E.g.,
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
// Dequeue your cell and other code goes here.
// set the button's tag like below.
cell.innerCellButton.tag = indexPath.row
return cell
}
Close. I wouldn't use Suresh's method since it does not help find the IndexPath, which includes section and row.
First, I would recommend a model object for your table view data source. Learn more about the MVC pattern as well as parsing a JSON response to an object with mapping. However, this would give you the data you want.
func cellButtonTapped(cell: UITableViewCell) {
let indexPath = tableView.indexPathForCell(cell)
let resultList = self.items["result"] as! [[String: AnyObject]]
let itemForThisRow = resultList[indexPath.row]
let name = itemForThisRow["name"] as! String
}

Using a button in a cell to increment a value for that particular cell

How can I increment a value for a specific cell?
I saw this and this post. The prior I couldn't get to work (and is "brittle"?), and the former caused a segmentation fault with the functions incrementHandler() and decrementHandler().
class cell : UITableViewCell {
#IBOutlet weak var counter: UILabel
#IBAction func add (sender: AnyButton) {
counter.text = String(Int(counter.text) + 1)
// find it's corresponding stat value (from other class) and update it
stats[indexPath].counter = Int(counter.text)
}
}
class tableView: UITableViewController {
var stats = [Stat]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellID, forIndexPath: indexPath) as! ExpandedControllerCell
let stat = stats[indexPath.row]
cell.titleLabel.text = stat.name
cell.bigCounterLabel.text = String(stat.counter)
cell.smallCounterLabel.text = String(stat.counter)
return cell
}
}
In your cellForRowAtIndexPath, set a tag on the button. Then, in your IBAction method, use the tag to figure out which cell's button was tapped. Increment a value in an array, and then tell the cell at that indexPath to reload itself. (In cellForRowAtINdexPath, install the counter value into the cell.)
You can also write your button action so it uses the button's frame.origin to ask the table view which cell the button belongs to. That's better and less fragile.
See my answer in this thread for an explanation of how to do that.

Resources