Swift - User input mixed up when uitableview reuses cells? - ios

I am currently developing an app that features a UITableView with custom cells that contain a UITextField. The problem I am having is that, after the user inputs a number to the textfield, upon scrolling, the tableView reuses that cell and the user's previous input is initialized in the new cell. For example, if I put the number 7 in the top cell's textfield, when I scroll, the newest cell already has a 7 in it. I am wondering how I can fix this problem? Thank you for any help.
Edit: Since my problem is unclear, I basically need a way for my UITableViewCell to "talk" to the model in my UITableViewController so that when the user is done editing the UITextField in the cell, I can save the input to an array in my view controller. Thanks again

Override -prepareForReuse in your cell subclass.
In that method, set your text to nil and then call super.prepareForReuse

The quick fix is to create an array of integers to represent numbers in the table.
Then, in cellForRowAtIndexPath method, just do something like
cell.textField.text = numberArray[indexPath.row]
You need to actually save the text fields data into this array now though, in the same method add
cell.textField.addTarget(self, action: "onTextChanged:", forControlEvents: UIControlEvents.EditingChanged)
Create the ,,onTextChanged'' method like
func onTextChanged(sender: UITextField) {
let cell = sender.superview! as! UITableViewCell
let indexPath = self.tableView.indexPathForCell(cell)!
numberArray[indexPath.row] = sender.text.toInt()!
}

Related

Swift tableview changes the value of a random cell when one cell is edited

I have a long list of textfields so I am using a tableView.
this is how the screen looks like
When I insert some text in a textfield in one of the cells and scroll down some other cells get the same textfield value. I have no idea why this would happen.
This is my code now:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! RelatieToevoegenCell
cell.label.text = items[indexPath.row]
cell.textfield.placeholder = items[indexPath.row]
return cell
}
Your main problem is that you are keeping your data inside Views (UITableVieCell).
Cells are reused by UITableView to optimize performance - so even if you have 1milion rows in your table, only few UITableViewCells are in memory (as many as are visible on the screen usually).
When you are calling dequeueReusableCell it takes one of already existing cells and reuse it.
To solve this problem you need to keep your data separately in an Array and keep modified texts there.
Then you need to modify code you posted, to take data every single time you configure UITableView from your "dataSource".
Also a good pratcice is to reset UITableViewCell when it's reused, you can do this by adding coding inside prepareForReuse - but that's optional, as text will be set from your data source anyways.
I would also recommend to read this before you start coding further - it will allow to understand how UITableView works on iOS:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html#//apple_ref/doc/uid/TP40007451-CH7-SW1
Basically, you have to get and store the values in view-controller because due to the reusable behavior of UITableViewCell you lost the reference of the invisible cell with all the child reference.
So you can store the text-field value by the textViewDidChange action in RelatieToevoegenCell class.

Swift: How to iterate through all tableViewRows on button press

I have created a static tableView and want to save the data from each tableViewCell (all cells are different custom tableViewCells) to CoreData. How do I iterate through all TableViewCells on button press? I'm not using Storyboard so i do not have IBOutlets.
EDIT: Sorry, maybe i should specify my problem. See the code below.
I have 3 CustomCells. FirstCustomCell includes a Textfield, SecondCustomCell a UIPickerView and ThirdCustomCell a UIDatePicker. The controller also includes a saveButton(). If i press the button i want to get the inputs from the TextField, UIPickerView and UIDatePicker.
//TableViewController
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return tableView.dequeueReusableCell(withIdentifier: cellId1) as! FirstCustomCell //includes a UITextField
case 1:
return tableView.dequeueReusableCell(withIdentifier: cellId2) as! SecondCustomCell //includes a UIPickerView
case 2:
return tableView.dequeueReusableCell(withIdentifier: cellId3) as! ThirdCustomCell //includes a UIDatePicker
default:
return UITableViewCell(style: .default, reuseIdentifier: nil)
}
}
How can i access the input from the TextField, UIPickerView and UIDatePicker on saveButton() in my TableViewController?
You should not be storing data in your cells. You should have a model object that holds the data from the cells, and install your data into the cells. Then if the user edits that data you should be collecting the edits as they are made and applying them to your model.
Then you can persist your model to Core data cleanly and easily.
Right now you're using a static table view so you might be able to get away with the approach you are using, but that's not to say it is a good way to do it - it is most definitely NOT.
For a normal table view, there are more cells than will fit on the screen, and the table view recycles cells and reuses them as the user scrolls through them. When a cell scrolls off the screen, the settings in its fields get discarded.
Short answer: You DON'T iterate through all table view rows. Here is the reason:
It is a STATIC table view. This means you created all table view cells programmatically (because you don't use storyboard). This means you know exactly how many cells there are.
What you should do:
Declare properties whenever you have a STATIC table view.
If each table view cell has the UITextField/UILabel/UISwitch/UIButton... that holds the data you need, declare a UITextField/UILabel/UISwitch/UIButton... property and access its data to save to core data.

Configuring UICollectionView to keep the same cell view

I have a collection view delegate
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
}
I wanted the property of the cell to persist between each call to this method.
let cell = levelView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,
forIndexPath: indexPath)
But retrieving cell using dequeueReusableCellWithReuseIdentifier seems to create a new cell view element that is different from the initial one.
cell.accessibilityIncrement()
So the cell accessibility value cannot persist after tapping on the cell.
I need to do this because I wanted to do UI testing by tapping on the cell.
Any help is appreciated!
Reusable cells in iOS are handled with a queue. This helps the tableViews and collectionViews move smoothly and remain efficient with a large amount of data. I don't think you would want to change the way that is designed to work in your application. This way, the device only needs to load as many cells into memory as can be displayed on a single screen. As cells move off the screen, they can be reused as another cell as cellForItemAtIndexPath will be called to load the required data into the reused cell.
If the goal is to have a value persisted between cell reloads, consider using a map or some other similar variable that could be managed by the collectionView's dataSource delegate.
For example, in your viewController that contains your dataSource delegate, you could have a Dictionary defined like so:
let numberOfTapsByIndexPath = [NSIndexPath: Int]()
Then every time a cell is tapped, you would increment the value in the map for the cell tapped. That code might look something like this:
let previousTaps = numberOfTapsByIndexPath[indexPath]
numberOfTapsByIndexPath[indexPath] = previousTaps + 1
I'm not 100% sure that was the goal you explained above, but regardless, you'll want to move any persistent information out of the cell and into a variable that the dataSource delegate or some other singleton can manage.
Hope this helps!

UISwitch in Table Cell -- determine selected indexPath

I'm using a UISwitch in the accessoryView of a UITableViewCell.
The switch uses target-action to communicate when it has been selected or deselected:
switchV.addTarget(self, action: "onChangeSwitch:", forControlEvents: UIControlEvents.ValueChanged)
The problem is figuring out which switch was selected. I know of 3 methods. Each is unsatisfying somewhow.
1. I can use tags:
switchV.tag = indexPath.row
But this sucks if you have sections (and I do), because I need to parse it into and out of a two-number section/row format.
2. I could use the data model and store the switch view on the data item that the cell is drawing:
dataItem.switch.addTarget(self, action: "onChangeSwitch:", forControlEvents: UIControlEvents.ValueChanged)
cell.accessoryView = dataItem.switch
I can then identify which switch was selected by looping over my data set and doing an identity match with sender. This is a lot of looping, and I don't want to put views in my data model.
3. Then there's this method that uses the coordinate of the switch to find the row. Stateless-ish, involves no string parsing or data model muddying, but coordinates, rly? Can I trust it?
tableView.indexPathForRowAtPoint(
sender.convertPoint(CGPointZero, toView: tableView)
)
Is there a better way to get the indexPath of the UISwitch that is selected?
Method #3 is pretty good in my opinion. However if you want it to be really clean here's what I would do .
Declare a custom TableviewCell say CustomTableViewCell and have a delegate protocol called CustomCellDelegate. Here the delegate gets informed like so :
-(void)switchChanged:(UISwitch*)switch inCell:(CustomTableViewCell*)cell
In cellForRowAtIndexPath set your view controller as the delegate of the cell.
Add the switch to to your custom cell and make the cell as the target and implement the switch's action method. Inside the action method call the delegate :
-(void)switchChanged:(id)sender {
if(self.delegate && [self.delegate respondsToSelector:#selector(switchChanged:inCell:])) {
[self.delegate switchChanged:sender inCell:self];
}
Now in your viewController use the passed in cell in the delegate method to calculate the index path :
-(void)switchChanged:(id)sender inCell:(CustomTableViewCell*)cell {
NSIndexPath *path = [self.tableview indexPathForCell:cell];
}
Its a bit of work but if you want to do it the proper way , you can .
Another option is to use an hash which maintains the switch -> data link
var switchToData=[UISwitch:yourData]()
in cellForRowAtIndexPath:
switchToData[newSwitch]=myData
in onChangeSwitch
dataChanged=switchToData[switchChanged]
The hash will be quite small, the same size of the visible switch....

UICollectionView Cells Are Not Retaining Data --- How Can I Make Their Data Persistent?

I have a UICollectionView full of custom cell objects.
When the user selects one I draw a circle and put a number in the middle.
However as the user scrolls down and then back up, selected cells have their number data all messed up.
I dove deeper into what the problem is, and it turns out that cells are not cached, and when you scroll up with a UICollectionView, its creating new cell assets, as such data inside them is destroyed (and hence I can't store a number in them). So my thought is to put the number back each time that cell is back on the screen, but I can't tell when a cell is being displayed or not.
I've made a map which records the NSIndexPath of selected cells. However, I don't know how to tell if that path (i.e. a cell at that path) is currently being displayed.
Do you know how I can tell if a cell is being displayed from an NSIndexPath at any point along a scroll?
Here is my cell code for selection (my UICollectionView calls this).
override public var selected: Bool
{
didSet
{
_checkMark?.hidden = !selected
_number?.hidden = !selected
println("did set")
}
}
func setNumberText(number:UInt8)
{
if(_number == nil)
{
_number = UILabel(frame: CGRectMake(0,0,1,1))
addSubview(_number)
}
_number.text = String(number)
_number.sizeToFit()
_number.frame = CGRectMake(_checkMark.frame.width/2 - _number.frame.width/2, _checkMark.frame.height/2 - _number.frame.height, _checkMark.frame.width, _checkMark.frame.height)
_numberInt = number
}
Is there some "cellAtPathIsNowOnScreen" event I can track?
You're doing it wrong. Cells get reused as you scroll, and the method by which this happens is an implementation detail that you don't get to know.
Instead you should store the data about what the cell should draw, in this case the number, in the data source for your collection view. Then in -
collectionView:cellForItemAtIndexPath: you can tell the cell what number to draw after you dequeue it, or clear the value if the number shouldn't be drawn for this particular cell.
Additionally, UICollectionView does have a - visibleCells method which will return an array of all the cells that are currently visible, but that's really not what you want to do.

Resources