UITableViewCell Data changes after scrolling - ios

I am creating a form in UITableViewCell, this form has lable and text field, in prototype cell. See its image here
TableView Story board Image.
I am using identifier to dynamically create the form, my code is
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("protocell", forIndexPath: indexPath) as! UITableViewCell
var lable = cell.viewWithTag(1) as! UILabel
lable.text = details[indexPath.row]
lable.textColor = UIColor.brownColor().colorWithAlphaComponent(0.7)
var textfield = cell.viewWithTag(2) as! UITextField
textfield.placeholder = "Enter \(details[indexPath.row])"
self.arrayTextField.append(textfield)
if details[indexPath.row] == "Name" {
textfield.placeholder = "Enter First \(details[indexPath.row]) Middle \(details[indexPath.row]) Last \(details[indexPath.row]) "
}
textfield.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.2)
return cell
}
Now the problem is I am having 18 fields and when I enter values in the field and scroll the the view to fill the remaining fields, the values changes in the fields changes.
Please help.

Create UITableViewCell subclass and override prepeareForReuse function - to turn cell to default mode.
Swift:
override func prepareForReuse() {
super.prepareForReuse()
//set cell to initial state here, reset or set values, etc.
}
As per your comment - of how to subclass UITableViewCell:
import UIKit
class MyCustomCell: UITableViewCell {
// things that you can do here:
// initialise your properties here. (label, textfield. etc)
// layout subviews.
// override superclass APIs.
}

Do not store the textfields in an array instead datasource "details" array should do it.
Once you have entered any thing in the textfield update you "details" array for that indexPath.
Subclass UITableViewCell and make a CustomTableViewCell and have IBOutlets for you textfields labels and what not to make your life easier
Have a method on your CustomTablewViewCell like updateWithDetails(details) so that the code is well encapsulated

Related

Init custom UITableViewCell from nib without dequeueReusableCellWithIdentifier

SWIFT
I need to make an array of cells. I have few custom cell classes (inheritated from UITableViewCell) with nib files.
How to init cell without registering nib in tableview and doing dequeueReusableCellWithIdentifier?
I did it like this, but don't think, that it will work:
var labelCell = CustomCellClass.initialize()
I'm inferring from the discussion in comments elsewhere that the reason you want to not allow cells to be dequeued and reused is that you're having trouble keeping track of user input captured in the cells.
The bottom line is that you really should allow the cells to be dequeued and reused and just handle that appropriately. If you're having problems with cells being reused, this can be resolved by separating the “model” (i.e. your data) from the “view” (i.e., the UIKit controls). This is the spirit of the model-view-controller pattern, but is true in any of those patterns that have separation of concerns (e.g., MVVP, MVP, etc.).
The key is that as values change in the cell, your cell should immediately tell the view controller so that the view controller can update the model immediately. Then, when the view controller needs to later do something with the value associated with a particular row, it doesn't retrieve it from the cell, but rather from its own model.
So, I might define a protocol for the cell to inform the table view that its text field changed:
protocol CustomCellDelegate: class {
func cell(_ cell: CustomCell, didUpdateTextField textField: UITextField)
}
And I'd then define a cell class that called that delegate:
class CustomCell: UITableViewCell {
weak var delegate: CustomCellDelegate?
#IBOutlet weak var customTextField: UITextField! // hook up outlet to this property in IB
#IBAction func didChangeValue(_ sender: UITextField) { // hook up "editing changed" action for the text field to this method in IB
delegate?.cell(self, didUpdateTextField: sender)
}
}
Now, the view controller will:
register the reuse identifier with the NIB in question;
in cellForRowAt, populate the text field and specify itself as the delegate for that cell; and
handle the didUpdateTextField method to update model if user changes anything.
Thus, something like:
class ViewController: UITableViewController {
var values = ["One", "Two", "Three"] // some initial values
private let cellIdentifier = "CustomCell"
override func viewDidLoad() {
super.viewDidLoad()
// if you’re using NIBs, you register them.
// obviously if using prototype cells in your storyboard, this isn’t necessary.
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: cellIdentifier) // or use cell prototype with storyboard identifer specified
}
}
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return values.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CustomCell
// populate cell and specify delegate
cell.delegate = self
cell.customTextField.text = values[indexPath.row]
return cell
}
}
// MARK: - CustomCellDelegate
extension ViewController: CustomCellDelegate {
func cell(_ cell: CustomCell, didUpdateTextField textField: UITextField) {
// when the cell tells us that its text field's value changed, update our own model
if let indexPath = tableView.indexPath(for: cell), let string = textField.text {
values[indexPath.row] = string
}
}
}
Many people might be inclined to simplify this further, by hooking the IBAction for the text field directly to a view controller method. That works, and eliminates the need for this protocol, but the problem is that you need to figure out with which row this particular UIKit control is associated. The common trick is to navigate up the view hierarchy to identify the appropriate cell (e.g. often the text field will be in a content view within the cell, so you grab textField.superview.superview as! UITableViewCell), but that feels a little fragile to me.
But regardless of this little detail, hopefully this illustrates the broader pattern. Rather than trying to have cells keep track of user input, you should have the cell (the “view”) update the controller of any data changes immediately, and the view controller then updates the model immediately, and you no longer need to worry about the cell reuse optimizations that iOS employs.
For Swift 2 renditions, see previous revision of this answer.
The very idea of a static table is that it is fully defined in IB. One solution is to copy-paste those cells from their isolated NIBs to the one containing the table.
A better solution is to make the table dynamic, and have the dynamic code return a static number of sections and rows. Write the rest of the logic as if it's dynamic, (e.g. register all of the nibs, and initialize an array of cell identifiers how you want them organized in the table, use that array to dequeue).
Another way to solve it:
1) UITextField Delegate in View Controller. Store values in array
var textFieldValues = ["", "", "", "", "", "", "", "", "", ""] // with count of text fields in table. Init in top of ViewController.
//MARK:TextField Delegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string == " "
{
return false
}
let indexPathForCellWhereTextFieldIs = self.tableView.indexPathForCell(textField.superview?.superview as! UITableViewCell)
textFieldValues[(indexPathForCellWhereTextFieldIs?.section)!] = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString as String
return true
}
2) At indexPathForRow
cell.registrationTextField.text = textFieldValues[indexPath.section]
cell.registrationTextField.delegate = self
3) Get data from array
func tapOnRegisterButton(sender:UIButton)
{
debugPrint(textFieldValues)
}

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.

how to determine which cell's textfield was just edited in a collection view?

What property can I access to find out?
I was learning some swift from this tutorial, and I decided to give myself some problems to do. One was that in the tutorial, to edit a name, there is a UIAlert. I want to get rid of that and instead, have UITextField in place of the UILabel, so the user could simply tap on a name, and edit it. I would use the textFieldDidEndEditing(textField: UITextField) function to update the model, which is a dictionary of names and picture filenames.
I set the view controller as the UITextFieldDelegate, I put in the function, but now I'm stuck, because although the text was updated just fine in one of the cells, I don't know how to tell which cell it happened in.
In this case, you probably can consider to subclass a UITextField to refer a Dictionary item.
class DictionaryTextField: UITextField {
var item: [String : AnyObject]?
}
Also create a subclass of UITableViewCell to hold above DictionaryTextField as an IBOutlet property.
class TextFieldTableViewCell: UITableViewCell {
#IBOutlet weak var textField: DictionaryTextField!
}
After finished above setting, an item Dictionary of datasource can be set in func tableView(:, cellForRowAtIndexPath: ).
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! TextFieldTableViewCell
cell.textField.delegate = self
let item = items[indexPath.row]
cell.textField.item = item
cell.textField.text = "TextField \(item["name"]!)"
return cell
}
Later, in the UITextFiedDelegate, cast the textField as DictionaryTextField. Then the item can be retrieved directly.
func textFieldDidBeginEditing(textField: UITextField) {
guard let textField = textField as? DictionaryTextField else {
return
}
print("Did begin editing: \(textField.item)")
}
The revised codes can be downloaded again with this link: https://db.tt/8j9ENf7b

Populating text field in prototype cell

Currently I have a few of custom cell's prototypes created in Storyboard with text fields embedded in them. To access these text fields, I use nameTextField = cell.viewWithTag:(1) in cellForRowAtIndexPath:. But viewDidLoad: and viewWillAppear: methods get called before cellForRowAtIndexPath, so at that time nameTextField is nil. To populate text fields when table view shows on screen, I use viewDidAppear:, but it results in a noticeable delay. Also, when I scroll table view up and down, cellForRowAtIndexPath: gets called again and again, resetting already entered data in text fields.
Are there more efficient ways to populate text fields embedded in custom cells' prototypes with data just before the view shows up, and to prevent resetting of entered data in each cellForRowAtIndexPath: call?
I guess you're creating profile screen (or something with many textField to get input data from user). Am I right?
If I'm right, you can use a static tableView (when you have a few textFields)
Hope this can help.
I'm not sure I understand completely what you're trying to do, but cells are normally configured in the cellForRowAtIndexPath: method, not in viewDidLoad. You can also try connecting the textfield to an outlet on your custom cell class. Then you can do:
// in view controller
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
as! CustomCell
let object = myDataSource[indexPath.row]
cell.textField.text = object.description
cell.shouldBecomeFirstResponder = indexPath.row == 0
return cell
}
// then in the cell
class CustomCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var shouldBecomeFirstResponder: Bool = false
override func awakeFromNib() {
if shouldBecomeFirstResponder {
textField.becomeFirstResponder()
}
}
}
Then when users input text into the textfield, it would make sense to update your data source.
In viewDidLoad try to run something like self.tableView.reloadData before you do this line "nameTextField = cell.viewWithTag:(1)".

Stop the reuse of custom cells Swift

I have an uitableview with a custom cell which gets data from the array.
Custom cell has an uilabel and an uibutton (which is not visible until the uilabel text or the array object which loads for the text - is nil).
On launch everything is fine. When i press the uibutton the array is being appended, the new cells are being inserted below the cell.
But when i scroll - all of a sudden the uibutton appears on other cells where this conditional uilabel text isEmpty is not implied.
Here is how the whole process looks like
Here is my code for cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
if let text = cell.lblCarName.text where text.isEmpty {
cell.act1.hidden = false
} else {
println("Failed")
}
cell.act1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
cell.act2.setTitle(answersdict.last, forState:UIControlState.Normal)
return cell
}
So my general question is how do i stop the reuse of those custom cells?
As far as i'm aware there is no direct way of doing this on reusablecellswithidentifier in swift, but maybe there are some workarounds on that issue?
When a cell is reused, it still has the old values from its previous use.
You have to prepare it for reuse by resetting that flag which showed your hidden control.
You can do this either in tableView:cellForRowAtIndexPath: or the cell's prepareForReuse method.
Update:
Here's an example you can add for TblCell:
override func prepareForReuse()
{
super.prepareForReuse()
// Reset the cell for new row's data
self.act1.hidden = true
}

Resources