Swift: How to iterate through all tableViewRows on button press - ios

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.

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.

StoryBoard actions cannot be targeted at repeating content [duplicate]

I have just created an app and have started hooking up #IBOutlet's to the storyboard. I am connecting some of them to labels in a UITableViewCell Prototype Cell with a Basic Style. When I connect it though I get this error in the Storyboard:
The detailText Outlet from the TableViewController to the UILabel is invalid. Outlets cannot be connected to repeating content.
Can someone help me out? I have set it up the way I always do successfully but this time it has chucked me this error.
Create a table view cell subclass and set it as the class of the prototype. Add the outlets to that class and connect them. Now when you configure the cell you can access the outlets.
There are two types of table views cells provided to you through the storyboard, they are Dynamic Prototypes and Static Cells
1. Dynamic Prototypes
From the name, this type of cell is generated dynamically. They are controlled through your code, not the storyboard. With help of table view's delegate and data source, you can specify the number of cells, heights of cells, prototype of cells programmatically.
When you drag a cell to your table view, you are declaring a prototype of cells. You can then create any amount of cells base on this prototype and add them to the table view through cellForRow method, programmatically. The advantage of this is that you only need to define 1 prototype instead of creating each and every cell with all views added to them by yourself (See static cell).
So in this case, you cannot connect UI elements on cell prototype to your view controller. You will have only one view controller object initiated, but you may have many cell objects initiated and added to your table view. It doesn't make sense to connect cell prototype to view controller because you cannot control multiple cells with one view controller connection. And you will get an error if you do so.
To fix this problem, you need to connect your prototype label to a UITableViewCell object. A UITableViewCell is also a prototype of cells and you can initiate as many cell objects as you want, each of them is then connected to a view that is generated from your storyboard table cell prototype.
Finally, in your cellForRow method, create the custom cell from the UITableViewCell class, and do fun stuff with the label
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier") as! YourCell
cell.label.text = "it works!"
return cell
}
2. Static Cells
On the other hand, static cells are indeed configured though storyboard. You have to drag UI elements to each and every cell to create them. You will be controlling cell numbers, heights, etc from the storyboard. In this case, you will see a table view that is exactly the same from your phone compared with what you created from the storyboard. Static cells are more often used for setting page, which the cells do not change a lot.
To control UI elements for a static cell, you will indeed need to connect them directly to your view controller, and set them up.
If you're using a table view to display Settings and other options (like the built-in Settings app does), then you can set your Table View Content to Static Cells under the Attributes Inspector. Also, to do this, you must embedded your Table View in a UITableViewController instance.
Or you don't have to use IBOutlet to refer to the object in the view. You can give the Label in the tableViewCell a Tag value, for example set the Tag to 123 (this can be done by the attributes inspector). Then you can access the label by
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "someID", for: indexPath)
let label = cell.viewWithTag(123) as! UILabel //refer the label by Tag
switch indexPath.row {
case 0:
label.text = "Hello World!"
default:
label.text = "Default"
}
return cell
}
With me I have a UIViewcontroller, and into it I have a tableview with a custom cell on it. I map my outlet of UILabel into UItableviewcell to the UIViewController then got the error.
As most people have pointed out that subclassing UITableViewCell solves this issue.
But the reason this not allowed because the prototype cell(UITableViewCell) is defined by Apple and you cannot add any of your own outlets to it.
Sometimes Xcode could not control over correctly cell outlet connection.
Somehow my current cell’s label/button has connected another cell
I just remove those and error goes away.
For collectionView :
solution:
From viewcontroller, kindly remove the IBoutlet of colllectionviewcell
. the issue mentions the invalid of your IBOutlet. so remove all subclass which has multi-outlet(invalids) and reconnect it.
The answer is already mentioned in another question for collectionviewcell
Click on simulator ,
Navigate to Window and enable Device Bezels

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!

UICollectionViewCells deque returns wrong cell

I have a CollectionView, I use the cells to display videos and I also store some other data on variables. And whenever I scroll I reuse previous cells using the following code:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("profileCell", forIndexPath: indexPath) as! ProfileCollectionViewCell
But I noticed that when I am using an indexPath I have previously used, I do not get back the same cell as previously but another cell apparently at a random order. I would like to avoid reloading the videos when it is not necessary.
Is there a way for me to get the same cell used with the same indexPath?
Thanks,
Carlos
That's how collectionView.dequeueReusableCellWithReuseIdentifier works.
If you want to go around this functionality you could save all cells that are created in an array and then instead of dequeueing you could fetch the one with the video already loaded. However, if you use some framework that caches the videos after loading them you shouldn't need to deal with this at all.
var cells: [CustomCollectionViewCell] = []
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell:CustomCollectionViewCell?
if cells.count >= indexPath.row {
cell = cells[indexPath.row]
} else {
cell = CustomCollectionViewCell()
//setup cell
cells.append(cell)
}
return cell!
}
Disclaimer:
This solution is a hack. As mentioned by #Async in the comment below this solution will use a lot of memory if you create a lot of cells.
You are using reusable cells. With that you can't be sure you got always the same cell, it's almost always the other way around. You could try with storing the cells when first loaded, then checking it in cellForRow.. but in my opinion better way would be to store videos to file (of course with a limit), because storing cells isn't the best option.
You cannot influence which cell instance you get back from dequeueReusableCellWithReuseIdentifier. So you cannot keep the video in the cell when the cell is scrolled offscreen.
What you can do is add a NSMutableDictionary property to your view controller and store references to your videos in it (use the cell's indexPath as key). You can store the references in
tableView(_:didEndDisplayingCell:forRowAtIndexPath:).
Then when you get a cell from dequeueReusableCellWithReuseIdentifier you retrieve to video from the dictionary and add it to the cell. That way it does not matter which cell you get back from the queue.

Swift - User input mixed up when uitableview reuses cells?

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()!
}

Resources