I think I'm not lucid anymore...
I want to display data - retrieved from a server - in a UItableView. To do so I created
A wrapper class to store data elements collected from a server. All of them are then stored into an array.
A UITableViewCell subclass to display the data into a custom cell
A Xib file, associated to the UITableViewCell defined in point 2)
In cellForRowAtIndexPath: , I dequeue instances of 2+3) an associate the fields of 1) to their counter parts in defined in 2).
To me this looks like a lot of stuff.
But removing 1) seems difficult, as dequeueReusableCellWithIdentifier: manages it's own pool of objects, so it doesn't really make sense to create an array of UITableViewCells in step 1)
Removing 3) may be possible, but adds extra over-head when setting up the cell in cellForRowAtIndexPath:
Anyone has found a way to trim-down on this class crowd ?
If your cell is not too complex, then you can create it by code in the controller you are displaying data.
If your cell has a higher complexity, then there isn't really a way to trim it down. Besides 2 classes + a resource file is not that much really for what you are trying to accomplish.
Related
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.
I have a bunch of different UITableViewCell types. Performance-wise, would it be better to separate them into different classes, or to have one central class and change around some attributes and views programmatically?
The reason I ask is because in cellForRowAtIndexPath you dequeue your cell, and if you dequeue a bunch of different classes, wouldn't that make reuse difficult as there would be a massive divide of classes in the reusable "pool" of cells?
You should create a class for each UITableViewCell type that you have. That's best practice for a number of reasons, including, but not limited to:
1) Modularity of code (You can modify each cell easily when the need arises)
2) Simplicity (Having one class for multiple different types of cells would be a headache with all the if/else statements etc...
3) There won't be any memory issues
Why won't there be memory issues or as you describe, "a massive divide of classes"? The UITableView will only allocate as many cells as it needs to fill the tableview when it appears. As you scroll, the cells that were allocated at first, are now queued for reuse. If an instance arises where the cell can be reused, it will be, thereby avoiding another allocation, and thereby avoiding a UI slowdown. So, at any one time, regardless of how many UITableViewCell classes you have (unless you have thousands), there will be relatively few cells in the queue waiting to be reused.
To add to user1840001's excellent answer (voted) :
You should use different classes when your cell STRUCTURE varies in any non-trivial way.
Use the same cell class if you have the same structure (set of fields) but different content to display in that structure.
EDIT: Looks like my up-vote didn't "take." NOW I upvoted #user1840001's answer...
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.
I have a custom UITableViewCell class that I use to display quite a complex set of data.
Essentially the cell displays a Match object. But in doing so it displays information about the two Teams, the score, the time elapsed and so on.
Thinking about MVC and clean code.
Should I just pass in the Match object and let the cell do everything? Or is it better practise to expose the different elements of the cell (team1NameLabel, team1ScoreLabel, team2NameLabel, etc...) and set them all individually in the UITableViewController?
The first way makes the UITableViewController cleaner but then I'm relying on the UITableViewCell to "know" about the Match class, the Team class etc...
The second way makes more work for the UITableViewController but then makes the UITableViewCell a "dumb" display. All it does is then lay out the information within the cell. It doesn't know anything about the information it is displaying.
I would follow these rules:
The cell should just have the outlets for displaying the various bits of data. It is a view so it should not contain any logic.
The controller should get the Match data, parse and make calculations if necessary, and populate the cell. It is a controller, so that is its primary function in a MVC context.
IMO it is better and more MVC-like to pass the Match object to your table view cell.
Lot of the code you find on the internet(even Apple examples if I remember well) is not doing that. You can see many times a configureCell method within the view controller that is called in tableView:cellForRowAtIndexPath:.
I prefer to pass the model object object to the cell, it makes my view controller code simpler and also it is simpler to unit test: when I test my view controller I only verify that the model object is passed to the cell, and then in the table view cell tests I verify that the test of the labels is set to the expected values. Someone may say that this is making the view knowing about the model, but I don't see any big problem on that.
Both ways are fine, but personally I would go for second option, i.e. table view exposing #property and, if necessary, outlets.
However, if you really want to go for the first option, I would suggest to have any objects passed to the cell to implements a protocol exposing few methods:
#protocol tableViewCellProtocol
-(NSString*)titleForCell;
-(NSString*)descriptionForCell;
Then you can "pass the protocol" rather than the object.
[mytableCell renderObject:objectImplementingProtocol];
This way you slightly decouple the objects itself, and prepare cell for reuse with other objects.
In my app, I use custom UITableViewCells loaded from a XIB. Each cell contains 4 objects, like a UIImageView, UILabel, UITextField, etc...
I set the tag of each object in my cellforrowatindexpath so each object has the same tag which also is the row of the cell in the tableview.
The problem is, how am I suppose to access these objects in a cell if they all have the same tag? How would I properly access these objects in a method that doesn't have a NSIndexPath parameter?
Thanks!
I personally try avoiding the use of tags as I usually overuse them to the point of utter confusion and have begun finding more elegant ways around it. For instance, in your situation I would create IBOutlets for the custom UITableViewCell's objects making them more semantically relevant. However, your situation might be different - there is not enough in the question for me to say this would definitely work for you.
That said, it sounds like you could accomplish what you're trying to do by:
Giving each cell returned by the cellForRowAtIndexPath method a tag (before returning it)
Giving each of the objects within the cell a tag, as you seem to have already done
That way you can access the cell's objects (presumably UIViews) from (anywhere) in your UITableViewController by:
UITableViewCell *cell = (UITableViewCell *)[self.tableview viewWithTag:CELL_TAG];
UIView *objectInCell = [cell viewWithTag:OBJECT_TAG];
Keep in mind that this would allow you to access an object in a specific cell. If you were trying to access the objects of all your cells, you could simply iterate through all of them. However, I sense that this would start becoming unnecessarily complicated at this point. It would help if you posted up a bit more information on what exactly you were trying to achieve by utilising the tagging this way. Hope this helps in the meantime.
No idea why one would number all the tags in a row with the same number, but I suppose you have your reasons ([cell1 viewwithTag:0] and [cell2 viewWithTag: 0] should be more than enough to differentiate them.)
Giving all the elements the same tag generally defeats the purpose of tagging them in the first place, though if you MUST include the row number in the tag (and have some idea about the size of the result set), you could tag each element with a ((multiple of size) + item# ) integer (e.g., if you know the table will hold fewer than 10,0000 results, you could tag each element as 10001, 10002, 10003 and 10004 in the first row, and 20001, 20002, 20003 and 20004 in the next, etc... Then to address the third element in the 4th row, you would use [view withTag: 40003].
You can access the row of the selected cell with this methode:
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]
but i am not sure if this answers your question. Please be more concrete.