Our app has a social feed with a name label similar to Facebook with a FeedController and FeedCell class. Each time we scroll back to a previously loaded cell, it reloads, so the name label becomes darker, the text becomes bolder and starts to overlap. How do we fix this? We tried the prepareToReuse method and setting the cell's label to nil, but that did not help. Any help would be greatly appreciated!
It appears that you're regenerating the label and adding it to your cell's content view every time that the label is reused.
Inside your FeedCell class, make sure to only initiate your interface elements in your initializer. From there, you can fill your cell with data in the cellForRowAtIndexPath: method of your UITableViewDataSource.
Inside cellForRowAtIndexPath:, make sure to not add any views to the cell as this could contribute to the issue you're describing. As a rule of thumb, I make sure to initialize the views in the cell subclass's initializer, and only manipulate properties of these views (like hidden, alpha, etc) when working with the cells.
Moving forward, you can use prepareForReuse to clear any data from your cell that may be leftover from the previous time the cell was used. In many cases though, this isn't necessary as the data will be changed in cellForRowAtIndexPath:.
Related
I'm having some issues with reusable cells in a UITableView. I have several types of cells, that I declare in the constructor.
My issue is that I have one particular type of cell that contains a UITextView and I have an issue when I scroll the table, the text within is lost. I need to save this text to the models that accompany the cells and then put the text back when the cell is used again.
How do I know that the cell is being moved away from? I have other types of cells, so I need a way to invoke some code to do the saving part on the scroll of the UITableView.
I hope that makes sense, if more is required, let me know.
Thanks.
Just save the text once it is changed to the model, check if any text is present and use that in tableView(_:cellForRowAt:)
For more help you will have to show us your code.
You can inherit UITextViewDelegate and in the textViewDidEndEditing(_:) check if the text view is edited, then you will be able to store the text in a variable or somewhere else and restore it whenever you are about to show that cell again.
If there is more than one text view, you might want to set an accessibility identifier for each kind, so you will find out which one did end editing.
I am just thinking. Suppose, i have a tableview which have custom cell. It's simple. But My idea is that, when i click a tableview cell then another tableview is appear under that tableview cell, and again i click that cell then that sub tableview disappear. Similarly when i click second cell than work same. Is it possible? Please Provide me any idea or reference.
This is entirely possible, you're talking about Expandable cells.
My example here
The general idea is that your custom cell has a tableview at the bottom of the cell, and what you do is just change the cell height to display said tableview, on tap.
It's not easy, I'm not gonna lie it took us a while to do it, but we managed, and I'm telling you, it's very possible.
You can find a lot of help using the Expandable Cell keywords.
Note that you're gonna find yourself handling a lot :
What to do when the expanding cells is shown off screen?
What to do when you're expanding the first/last cells ?
What to do when expanding another cell ?
What to do when scrolling inside that cell (a scrollview inside a scrollview !)
There are many cases where it'll work, but won't work fine, and there is gonna be a lot of fine tuning. Specially in our case where we have rounded corners, but only when the cell is expanded, and not in cases where it's the last or first cell (next to section header).
They look cool and make you feel proud, but don't say to your PM it'll be done in a week, because it's a pain to build.
If you want to show additional cell information, you can add more cells after the cell indexpath you have clicked.
Create a custom table view cell classCustomTableViewCell by subclassingUITableViewCell class. And system will generate CustomTableViewCell.h, CustomTableViewCell.m, CustomTableViewCell.xib files for you.
Add protocols UITableViewDataSource and UITableViewDelegate in your CustomTableViewCell.h and implement the required methods in CustomTableViewCell.m files
Add a method for setting datasource and use the datasource for updating the table.
NOTE:
Handle table-dequeue mechanism properly, otherwise you will end up
with weird issues that may take time to investigate and resolve.
If you use this custom cell for all the cells in your parent table then the gestures will only listened by the child table. So plan for that too.
Please visit my blog for the sample code. https://myioslearnings.blogspot.in/2017/03/nested-table-view-in-ios-objective-c.html
I have an averagely sized table which is working perfectly, except for the row insertion animation (withRowAnimation). I've overriden it (with the help of stack community) to have a longer duration than the original system animation and it works just fine, but ...
Because i'm using custom cells as reusable cells - each time i scroll this effect is getting wiped out.
So the only solution i see is to stop the reuse.
I know this will interfere with the memory, but in this case its the only scenario left (of which i know).
My general question is how do i load a custom cell nib without using dequeueReusableCellWithIdentifier so that the reuse wouldn't happen.
Thank you.
You should use dequeueReusableCellWithIdentifier, not using it is a recipe for disaster.
Take a look into your code and when you dequeue a cell (reused or not, most of the time it'll be reused), the first thing you should do is set all elements in that cell back to the original state, so that that reused cell behaves like a non reused cell.
I don't know what you animation/implementation is about but here an example from a project I wrote with custom cells :
In each cell I added buttons, this could be 1, 2, 3,... buttons. If I did nothing in a reused cell I would have more buttons than expected because the old buttons would still be there...
So in my code the first thing I did after dequeing the cell would be to remove all the buttons.
let cell = tableView.dequeueReusableCellWithIdentifier("RelatedCell") as! RelatedCell
cell.removeAllActionButtons()
where my removeAllActionButtons (a method in my custom cell) method would be something like:
for button in actionButtons {
button.removeFromSuperview()
}
actionButtons is an Array containing all that buttons added to that custom cell
I don't want cells to have to be generated when they come on screen for the first time. I also don't want cells that have gone off screen to have to be regenerated when they come back on the screen.
How can I have all the cells generated on loading the UICollectionView and stay that way as I scroll up and down?
You could manage your own list of cells (pre-create them based on the datasource, create any new ones as the datasource updates, and return the appropriate cell for the indexPath desired instead of dequeuing a cell inside cellForItemAtIndexPath) but as a general rule, that's a bad idea. You may have some specific case where cell configuration performance is poor, but the answer is usually "improve cell configuration" and rarely "keep everything in memory".
Speculating: If you're thinking of doing this in order to preserve state or cache some information in the cell, that's the wrong place to put it. The cell is presentation; keep that info in the datasource element so that whatever needs to be represented can be applied to whatever cell is being used right now.
I want to load a tableViewCell from the storyboard without using dequeueReusableCellWithIdentifier to create a prototype cell to reference before cellForRowAtIndex is called. Calling that outside of CellForRowAtIndex does funny stuff.
dequeueReusableCellWithIdentifier is still being used in cellForRowAtIndex as it normally has been.
I dynamically set the cell heights based on it's contents. This requires me to know where the size, positions, text attributes like font sizes, alignments, etc of views in the tableViewCell. Otherwise I have to hard code these values to match what's in the storyboard.
What I'm currently doing is create a new xib file with just the cell, load it from viewDidLoad, and keep a pointer to it.
-(void)viewDidLoad {
// typical coding stuff goes here
// load nib
UINib *nib = [UINib nibWithNibName:#"ContentTextCell" bundle:nil];
// assign nib to identifier
[self.tableView registerNib:nib forCellReuseIdentifier:#"ContentTextCell"];
// reference cell
NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
_referenceContentTextCell = [topLevelObjects objectAtIndex:0];
}
Is there any way for me to load the tableview cell without make it's own nib? Using dequeueReusableCellWithIdentifier:forIndexPath: causes the tableView to behave in an incorrect way.
Additional notes
I think a lot of people are under the impression I call the code on cellForRowAtIndex. It's called in viewDidLoad. It was always displayed as such but perhaps it was easy to understand when skimming the question. I also still use dequeueResuableCell as normal in cellForRowAtIndex. Just wanted to make that clear.
The row heights are dynamic. If you made the suggestion that I should make the row height 44 then you may want to read the question more carefully before attempting to answer the question.
The text I'm using is from a json file, it requires the paragraph, font and positioning of a textView to calculate the text height, which impacts the row height. I'd like to pull these pieces of data from the prototype cell rather then hard coding the values, and making sure they match what's in the storyboard.
The code already works as is. It runs fine. I just think it'd be more convenient to be able to pull the prototype cell from the storyboard rather than making a new xib for it.
If I understand correctly, the row height is a function of your model and some attributes of subviews in the cell, for example, a string from your model and the font size in the cell's text view.
I agree that keeping the view attributes of the cell's subviews in code seems redundant with keeping them in the storyboard, but you're also right that keeping a reference cell around is weird. It's weird whether you get the cell from a nib or whether you find a way to get it from the storyboard (which I don't think you can, which is the answer to your stated question).
Dequeueing a cell in heightForRowAtIndexPath makes little more sense and probably causes an infinite recursive loop as I'm sure you've found.
So you probably won't love this answer, but I think best idea is "redundancy with an attitude change". "Redundancy" means that you keep the view attributes in code, like a method that returns the textView's desired font pointSize. "Attitude change", means you don't think of this as redundancy. Instead, your code should be the authority on all of the view attributes pertinent to row height. Think of the prototype cells in the storyboard as just a way of visualizing what the correct coded values should be. I'd even recommend setting the attributes in code using the coded values when you configure the cell, especially if there aren't too many of them.
Finally, if your rowHeight calculation is elaborate, and it sounds like it is, be sure to also implement
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
and do a much quicker calculation therein (like return a constant).
I've just been working through an almost identical problem. You want the height, but the height depends on a bunch of the cell UI setup. It might even be a fairly simple task of checking the padding height above and below an image. Something which could get tweaked in IB several times before you are done so you don't want to duplicate the padding value in code, you want to check them against the current values in IB.
You can call dequeueReusableCellWithIdentifier: from anywhere (assuming you have a reference to the UITableView). So you don't need another way to get the cell. This will not cause recursion or infinite loop problems.
There is a catch though. The cell won't go back to the auto-reuse pool. A UITableView figures out which cells scroll off the screen and puts them in the pool, but this cell never goes onto the screen and so the table view never remove it from the screen and put it back in the pool. To make matters worse, the table still has a strong reference to it; so if you lose your reference, it's not going to get cleaned up, it's essentially leaked memory. You can't use it, the table view can't use it and it will stay in memory until the backing UITableView gets deallocated.
I found a few ways around problem:
Dequeue the cell once, store it someplace safe and use the same one every time you need to test out some cell formatting. You can even call prepareForReuse: to clean it up every time you want to test something new. Holding a single extra cell in memory is unlikely to be a killer for most apps.
Sneak it back into the re-use pool. Wait, what? Yeah, I said you can't do it, but you can, but the thought makes me cringe a little. Next time you get to cellForRowAtIndexPath: don't dequeue a new cell, use the one you've got laying around and return that one instead. This will slip your wayward cell back into the fold and it will eventually make it's way back into the reuse pool.
So you can do this.... should you? Well, that's a different question. Apple has provided some alternatives to try and get around this issue, but in some cases they just don't work effectively.
That's a bad pattern and you will start to get undesired behaviours which you have already seen some of. It might be better to store the height against the cell as a class method.
+ (CGFloat)requiredHeight;
{
return 44.f;
}
and call it as such
self.tableView.rowHeight = [OKACell requiredHeight];
This does mean you need to manage the height in two places but because of the descriptive name and class method it shouldn't be difficult to change when that time comes.