UITableview cell reinitializing every time in iOS 7 - ios

All,
I hope most of you know that with ios7 there is not need to do a null check for tableview reuse
if (cell == nil) {
But unfortunately, because of that the cells are always reinitialized, as we put the code in the same method for initializing values. The problem is only with text fields inside the tableview though.
Let me explain the scenario. I have a table view with multiple rows, and some rows contain multiple text boxes. I populate the textboxes with data from server when the page is loaded. Since the cells are always re-initialized as i explained above, whatever I enter in the field goes away and the server data is re populated once i scroll down and come back to the initial stage. This is because the populating the data code is also in the same place. After fetching a reusable cell it populates the data.
Previously till ios6, we used if(cell==nil) and hence we loaded server data inside the cell and when reusing the cell, this piece of code will never be called.
I have other dirty solutions, but would like to know if someone else has a graceful way of dealing this. Please help.

You just don't store any data in the table view cell but in the model that fills the table cell. This is always the way it should be done.
Looking from the MVC standpoint than the UITableViewCell is a view. Since it is reused by iOS you should use a model to the view.

Yes, this is the expected behavior of UITableView. For performance reasons, cells are reused. Thus, it is your responsibility to populate the views in a Table View Cell every time tableView:cellForRowAtIndexPath: is called.
The thing I don't understand from your question - are you making a network call every single time a cell comes into view? If so, cache the results somewhere. Or, if it's a small amount of data, consider just doing it all in one shot at the beginning (still need to be asynchronous though).
One thing I see a lot of developers do is move a lot of code into UITableViewCell subclasses, which sounds like a good idea because it's modular, but makes solutions for problems like this more difficult. Have the Table View Data Source manage the network calls.
If you need some inspiration, look at Apple's LazyTableImages sample.

Related

UICollectionViewCell does load only when on view?

I'm developing an Chat application where I have a UICollectionView to control the messages and I came to a situation I would like to confirm with you.
For exemple, let's say I have 60 items in this UICollectionView, but based on the size of the items and the scrolling options I set, only the last 10 items are visible on the screen, from 50 to 59.
Based on that, it seems I'm not able to get cellForItem at IndexPath 30, for example. Is that correct?
I would like to confirm that with you before creating a solution to go over the items that are already "on screen" and I need to check. Any ideas and solutions you have already implemented is appreciated.
Also, based on the information above if, for example, I need to move on item from index path 30 to 31, will I have problems if they are not "instantiated" in the screen?
Thanks in advance!
You seem to be mixing your model, controller, and view classes, which is a bad thing™ for exactly the reason you encounter here.
I take it you're trying to access data from the index 30 (basically) and say to yourself "Hey, I already added that in the 30th cell, so I will just use the collection view's method to get that cell and take it from there". That means, you basically ask a view for data.
That won't work, because, as others pointed out (but more indirectly), there are not 60 cells at all at any given moment. There's basically as many cells as fit on the screen, (plus perhaps one or a few "buffer" cells so rendering during scrolling works, I can't remember that atm). This is why cellForItem(at:) is nil for an IndexPath that refers to a cell not actually visible at the moment. Basically it works in a similar way to a table view. The collection view simply does not keep around stuff it doesn't need to render for memory reasons.
If you need anything from a cell (which is after all also a view) at this path, why don't you get it from whatever data object represents the contents of this cell? Usually that's the UICollectionViewDataSource.
That's how the paradigm is supposed to work: The UICollectionViewDataSource is responsible for keeping around any data your app may need at a given time (this may or may not reloading it or parts of it, your choice). The UICollectionView uses its collectionView(_:cellForItemAt:) method when a certain IndexPath becomes visible, but it throws that away again (or rather queues it again so your data source may dequeue it in collectionView(_:cellForItemAt:) and reuse it for another data set that becomes visible).
And btw, please don't use use the UICollectionViewDataSource's collectionView(_:cellForItemAt:) method to get the cell and then the data from there. This method is supposed to be called by the collection view and depending on how you reuse cells or create them, this might mess up the entire process. Or at the very least create view-related overhead. Instead, get the data in the same way your UICollectionViewDataSource would get in inside of the method. Wrap that in an additional method you rely on or the like. Or, even better, rely on the model object that the controller uses as well.
Edit in response to your comment:
No, I did not mean it's bad to use a UIViewController as a UICollectionViewDataSource for a UICollectionView. What I meant was that it's bad to use the UICollectionView to get data, because that's what the data source is for. In your question you were wondering why cellForItem(at:) gives nil. That method is defined on UICollectionView. You didn't mention your intention was to move items around (I'll explain in a second), so I assumed you were trying to get whatever data was in the cell (I know, "assume makes an ass out of u and me...", sorry :) ). This is not the way to go, as the UICollectionView is not meant to hold the data for you. Rather, that's your job, and you can use a UICollectionViewDataSource for that. This latter class (or rather protocol a class can adopt) is basically meant to offer an interface for UICollectionView to get the data. It needs that, because, as said, it doesn't keep all data around. It requests stuff it needs from the data source. The data source, on the other hand, can manage that data itself, or maybe it relies on some deeper class architecture (i.e. other objects taking care of the underlying model) to get this. That part depends on your design. For smaller scenarios having the data source simply have the data in an array or dictionary is enough. Furthermore, a lot of designs actually use a UIViewControllerto adoptUICollectionViewDataSource`. That may be sufficient, but be careful not to blow up your view controller to a monstrosity that does everything. That's just a general tip, you have to decide on your own what is "too much".
Now to your actual intention: To move around cells you don't need to get them. You simply tell the UICollectionView to move whatever is at a given index path to some other index path. The according method is moveItem(at:to:). This works even if cellForItem(at:) would return nil for one of the two index paths. The collection view will ensure the cells are there before they become visible. it does so relying on the data source again, more specifically its collectionView(_:cellForItemAt:) method. Obviously that means you have to have your data source prepared for the move, i.e. it needs to return the correct cell for the given index. So alter your data source's internal storage (I assume an array?) before you move the items in the collection view.
Please see the documentation for more info on that. Also note that this is basically how to move items around programmatically. If you want the user to interactively move them around (in a chat that seems weird to me, though), it gets a little more complicated, but the documentation also helps with that.
Based on your question. If the currently visible cells on screen are from 50 to 59, the cellForItem at IndexPath 30 will not be available. It would be nil. Reason being the 30the cell would have already been reused to display one of the cells from 50 to 59.
There would not be problem to move cell from 30 to 31. Just update your array/data source and reload the collection view.
You can access the cell only if its visible for non visible cell you need to scroll programmatically using indexpath:-
collectionView.scrollToItem(at: yourIndexPath, at: UICollectionViewScrollPosition.top, animated: true)

Preload tableView cells and prevent reloading

I already found entries with that topic on this page and also a website that provides a tutorial for that problem. But nothing worked very well.
The tutorial sad I should double the height of my tableView so cells loaded earlier, but with a higher tableView I never reached the last cells.
My problem is, I use a tableView to display my apps mainpage. This mainPage shows every time a topic and if its necessary for that topic it shows for example a cell with a map, one with pictures and sometimes not. Problem now, if I trying to scroll to my view its always lagging because it loads a map or this pictures. And scrolling back again because the loaded cells already deleted. I used a tableView because of the possibility to switch celltypes(mapCell, pictureCell, textCell) on and off.
I understand this feature, because of memory issues but in my case its not that much and it would be better if all my cells be preloaded and stay in memory until I change the topic.
Is there a swifty way to told my tableView to have this behavior?
Thanks a lot and nice greetings
I would suggest a different approach. Set up your table view controller to install placeholder images in your cells, trigger an async download that you cache to disk, and then update the cell with it's downloaded content if it's still visible when the download is finished.
There are various third party frameworks that do all this housekeeping for you.
Declare a cell array.
Follow these steps whenever change in your topic.
Clear you array
Create and configure all cells add to your array.
Return these cells to datasource methods using row index. Don't use tableview dequeue method here.

UITableView use dequeueReusableCellWithIdentifier or not?

I am wondering what exactly happens, when you dont use dequeueReusableCellWithIdentifier in the cellForRowAtIndexPath-method.
In one Project I am collaborating we have different types of custom UITableViewCells which all appear in one single tableview. So here we fill arrays with all TableCells that should be displayed. These arrays are not very big (10-15 Cells) so for us that way works even not using any identifiers for dequeueReusableCellWithIdentifier. The next question is how at all you could use identifiers resp. dequeueReusableCellWithIdentifier when using different Cells in one single section of an UITableView. Is someone here hwo can explain, what exactly happens in background? Regards Nils
The dequeueReusableCellWithIdentifier is something that reminds me of Flyweight pattern.
Since allocation and instantiation of a cell can be an expensive task, using this mechanism you have the opportunity to create only the first visible cells and later reuse them just changing their contents. Scrolling animation must be as fast as possible to give a good experience to the user.
Is it worth it? Yes and it basically comes for free, we just need to pay attention that some old data can be still present in a new visualization, the trick is to always implement the method -prepareForReuse() correctly, here, you can eventually wipe all displayed data before setting the new one.
If you want to use different cells in the same section is absolutely possible, also if they have different height. You just need to crate different cell identifiers, one for each cells and tie them somehow along with your data.
I usually map data to be displayed in struct (swift) or dictionaries along with a key for the cell identifier to be used.
If your type of cells are representing themselves while scrolling you should dequeue them.

How can I make my UITableView request the X next cells that are outside the visible table?

I would like to have my UITableView load 3 or 4 cells outside of the table so that any data to be shown there is already loaded when I scroll down.
I have some images, and data that must be downloaded before it can be shown in the cell.
This causes a visible delay before the images are loaded when scrolling.
I can manually trigger loading of this data by doing it in the UITableViewDataSource tableView:cellForRowAtIndexPath: method. I've done this before, but I'm curious if there's an easier way to do it.
Is there any way to expand the reusable cell pool, or adjust how the cells are loaded/recycled?
EDIT:
To clarify, I have lazy loading of images and data in place.
Everything works fine, I just wanted an easy solution to the "prefetching" problem.
Which can be solved in many ways that has nothing to do with the view itself. Right now you can easily see the images load as you scroll. I just wanted them to load right before they become visible.
You may be looking for an asynchronous table view that loads the data asynchronously.
Apple provides a sample app demonstrating this:
LazyTableImages
Of course, you could pre-cache the data and begin downloading data into your datasource before they scroll.
The general idea is that you are loading data into a datasource (that is separate from the UI), so you can do this at any time (and in the background). You can display temporary data or some type of loading image or spinner if the data isn't loaded yet.
If data of the cell will be loaded when cel becomes visible, you can programmatically scroll the table view by scrolling to the bottom cell and go back to the first cell without animation. Another way would be creating all the cells and placing them into array when your view controller is created, and feed the table from that array that contains already created cells. I think there is no way to extend the cell pool as you are asking. Hope this helps, Good Luck!

iOS iterate UITableView

I have a UITableView that collects data from a database. What I would like to know is if there is some way I can iterate in the UITableView collection and check the values of the cell? The reason I ask is because I would like to update each cell based on the current value that it has (change font, size, color, etc.). I've seen in another SO post regarding this topic, but since the cells are already created and their values are changed it is a bit harder for me. I was thinking of iterating through the UITableView before I call reloadData, but any other suggestions are welcome.
You should not iterate over the cells of UITableView, because some of them (in fact, most of them) may not be present until you request them. UITableView aggressively recycles its cells, so if a cell is not visible, it is very likely that you would be creating it from scratch only to put it back into recycle queue moments later.
Changing your model and calling reloadData the way your post suggests would be the right solution. iOS will ensure that it runs the update in a smallest number of CPU cycles possible, so you do not need to worry about the cells that are already created. This is also the easiest approach in terms of your coding effort.
A table view is for displaying data. The properties of your table cells should only be written to, not read from. The appropriate way of handling this situation would be to update your underlying model objects -- the objects that you use to populate the table view -- as the data changes, and then reload the affected rows.
The issue you'll encounter is that UITableView reuses table cells. Once a table cell scrolls off the screen, it's quite likely that the table view will reuse the same cell to display a different row.
This means it's fundamentally not possible to iterate over the table cells. When you need to refresh a row because its data has changed, you should call reloadRowsAtIndexPaths:withRowAnimation: (or reloadData if all rows have changed) and if the row is visible on screen, UITableView will call your data source methods and give you an opportunity to configure the cell for display.

Resources