To establish some context :
I have a UITableView with n cells.
When the user selects a cell they the cell expands and the user experience then continues within that cell.
There are some animations that take place within the cell.
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
//expand cell/increase cell height animated
//add a button to bottom of cell with target(didpressbutton:)
}
- (void)didpressbutton:(id)sender
{
//perform complex animating rearranging UI elements
}
At the end of the flow the user needs to comeback to the original tableview.
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
But the cell with the misaligned UI elements are still showing as it is dequeuing the old cells.
Is there any way for me to clear the cached cell or reinitialise them?
You are obviously misusing -prepareForReuse:. Implement this method to reset any state the cells have.
Related
I have used this tutorial to put a collectionView inside a UITableView. https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
The UITableViewController is the dataSource & delegate for both the UITableView & the different collection views in each cell.
The problem is that I want to dynamically hide the CollectionView and to change its height to 0 whenever the collectionview is empty.
To do so, I have this code in CellForRowAtIndexPath
if (patients.paraclinicImage.count == 0){
[cell.collectionView setHidden:true];
cell.collectionViewHeight.constant = 0;
} else {
[cell.collectionView setHidden:false];
cell.collectionViewHeight.constant = 80;
}
By having this code, the collectionView shows up correctly initially. However, when scrolling fast, I will sometime have the cell load with the correct cell height, however the collection view will be empty. Refreshing the cell fixes this issue. Removing the above line of codes also fixes the issue.
Here are two images showing how the cell looks when first rendered, and after multiple scrollings (and re-renderings of the cell).
Before scrolling issue looks like below image
But After scrolling issue seems to like below one
I would appreciate any ideas you guys might have.
Debug view hierarchy showing an empty collectionView with an appropriate cell height
It is because your cell height is not updated on fast scrolling. UITableView caches the row height of indexPath just to calculate the scroll area. You need to set constraints properly and adjust the height of cell in heightForRowAtIndexPath: method.
Apply the logic inside heightForRowAtIndexPath: and your problem will be solved.
Happy Coding!!
I had the same issue. Sometimes, my collection view inside a tableviewcell wouldn't show the collectionview cells. I solved it by calling collectionView reload on main thread. I don't exactly know the reason, as I have many background calls on each cell and was reloading the tableview on main thread itself. Still, had to reload collectionview on main thread.
DispatchQueue.main.async {
cell.colViewProperties.reloadData()
}
Consider an standard, vertically scrolling flow layout populated with enough cells to cause scrolling. When scrolled to the bottom, if you delete an item such that the content size of the collection view must shrink to accommodate the new number of items (i.e. delete the last item on the bottom row), the row of cells that scroll in from the top are hidden. At the end of the deletion animation, the top row appears without animation - it's a very unpleasant effect.
In slow motion:
It's really simple to reproduce:
Create a new single view project and change the default ViewController to be a subclass of UICollectionViewController
Add a UICollectionViewController to the storyboard that uses a standard flow layout, and change its class to ViewController. Give the cell prototype the identifier "Cell" and a size of 200x200.
Add the following code to ViewController.m:
#interface ViewController ()
#property(nonatomic, assign) NSInteger numberOfItems;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.numberOfItems = 19;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.numberOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.numberOfItems--;
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
}
#end
Additional Info
I've seen other manifestations of this problem when dealing with collection views, it's just that the above example seems the simplest to demonstrate the issue. UICollectionView seems to go into some kind of paralysed state of panic during the default animations, and refuses to unhide certain cells until after the animation completes. It even prevents manual calls to cell.hidden = NO on hidden cells from having an effect (hidden is still YES afterwards). Dropping down to the underlying layer and setting hidden there works, provided you can get a reference to the cell you want to unhide, which is non-trivial when dealing with cells that haven't been displayed yet.
-initialLayoutAttributesForAppearingItemAtIndexPath is being called for every item visible at the time of the call to deleteItemsAtIndexPaths:, but not for the ones that are scrolled into view. It is possible work around the issue by calling reloadData inside a batch update block immediately afterwards, which appears to make the collection view realise that the top row is about to appear:
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
[collectionView performBatchUpdates:^{
[collectionView reloadData];
} completion:nil];
But unfortunately this is not an option for me. I am trying to implement some custom animation timing by manipulating the cell layers & animations, and calling reloadData really throws things out of whack by causing unnecessary layout callbacks.
Update: A bit of investigation
I added log statements to a lot of layout methods and looked through some stack frames to try and find out what's going wrong. Crucially, I'm checking when layoutSubviews is called, when the collection view asks for layout attributes from the layout object (layoutAttributesForElementsInRect:) and when applyLayoutAttributes: is called on the cells.
I would expect to see a sequence of methods like this:
// user taps cell (to delete it)
-deleteItemsAtIndexPaths:
-layoutAttributesForElementsInRect:
-finalLayoutAttributes...: // Called for the item being deleted
-finalLayoutAttributes...: // \__ Called for each index path visible
-initialLayoutAttributes...: // / when deletion started
-applyLayoutAttributes: // Called for the item being deleted, to apply final layout attributes
// collection view begins scrolling up
-layoutSubviews: // Called multiple times as the
-layoutAttributesForElementsInRect: // collection view scrolls
// ... for any new set of
// ... attributes returned:
-collectionView:cellForItemAtIndexPath:
-applyLayoutAttributes: // Sets the standard attributes for the new cell
// collection view finishes scrolling
Most of this is happening; layout is correctly triggered as the view scrolls, and the collection view properly queries the layout for the attributes of cells to be displayed. However, collectionView:cellForItemAtIndexPath: and the corresponding applyLayoutAttributes: methods are not being called until after the deletion, when layout is invoked one last time causing the hidden cells to be assigned their layout attributes (sets hidden = NO).
So it seems that despite receiving all the correct responses from the layout object, the collection view has some kind of flag set to not update the cells during the update. There is a private method on UICollectionView called from within layoutSubviews that seems responsible for refreshing the cells' appearance: _updateVisibleCellsNow:. This is from where the data source eventually gets asked for a new cell before applying the cells starting attributes, and it seems this is the point of failure, as it is not being called when it should be.
Additionally, this does seem to be related to the update animation, or at least cells are not updated for the duration of the insertion/deletion. For example the following works without glitches:
- (void)addCell
{
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForItem:self.numberOfItems
inSection:0];
self.numberOfItems++;
[self.collectionView insertItemsAtIndexPaths:#[indexPathToInsert]];
[self.collectionView scrollToItemAtIndexPath:indexPathToInsert
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:YES];
}
If the above method is called to insert a cell while the inserted cell is outside the current visible bounds, the item is inserted without animation and the collection view scrolls to it, properly dequeuing and displaying cells on the way.
Problem occurs in iOS 7 & iOS 8 beta 5.
Adjust your content insets so that they go beyond the bounds of the device's screen size slightly.
collectionView.contentInsets = UIEdgeInsetsMake(-5,0,0,0); //Adjust this value until it looks ok
I changed the color of text for the cell clicked in the table. But after the cell is clicked, when i come back to table the text of cell has the original color. Could you give me an advice?
This is the code in "didSelectRowAtIndexPath"
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.highlightedTextColor = [UIColor blueColor];
Thank you
after the cell is clicked, when i come back to table the text of cell has the original color. Could you give me an advice?
You need to have the color for each cell stored somewhere other than in the table, so that you can reproduce the colors you want anytime the table redraws itself. Typically, you'll have some sort of data structure that stores the table's data, and that's usually the right place to save any changes the user makes. The table view's data source should have a -tableView:cellForRowAtIndexPath: method that sets the color according to what you've saved, along with any other cell attributes.
This is happen because the cells are reused, so lets say when you change text colour property of some cell it will be affected as you expect but when you scroll and that cell disappear off the screen it will be put to reuse pool and if it appears again on the screen table view takes some cell from the reuse pool but it's properties will be different so the colour won't persist.
You should keep somewhere, for example in NSMutableArray, info about which table was clicked.
You can add an index path to the array when you click the cell and in cellForRowAtIndexPath: check is this indexPath in the array and if it is change appropriate property.
The problem is that iOS throws away your cell if you scroll away and recreates it when it's needed (you scroll back to the cell).
If I were you, I would subclass UITableViewCell and overwrite
- (void)setSelected:(BOOL)selected animated:(BOOL)animated;
In there you would have
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected: selected animated: animated];
self.textLabel.textColor = selected ? [UIColor blueColor] : [UIColor blackColor];
}
Since iOS UITableView remembers which cell is selected, this should work fine, even when it's recreated.
The reason it's happening is what others are saying: cells are reused.
Storing selection state or color will work, however if you just need to make sure that selected cells have a different color for a label than non-selected cells, there's a way that does not require to use a supporting data structure.
You just need to check if the cell being setup at - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath is currently selected or not, and that can be achieved with [tableView indexPathForSelectedRow] if your table uses single selection, or [tableView indexPathsForSelectedRows] if it uses multiple selection.
The last case requires you to find the current indexPath in the returned array, and might be slower than using the supporting array.
But if the selection is simple, then this solution is probably faster, uses less memory and is easier to read (IMO).
I have a UITableViewCell with dynamic height based on textual content. In tableview:heightForRowAtIndexPath: the height gets calculated. This works well.
When the cell enters editing mode with editingStyle UITableViewCellEditingStyleDelete it indents the cell a little and can push the content out some causing the rowHeight to change. Again this works well as switching the tableview to editing causes the table to relayout and therefor the rowHeight gets recalculated.
When you click the red button or swipe the cell a Delete button appears on right hand side of the cell. This however does NOT trigger a relayout of the table, only the cell itself. The problem here is that if the content goes over the bottom edge the cell does not get resized.
Is there a way to trigger a re-layout of the table when the delete button appears?
In general, height of UITableViewCell can be only setup via tableview:heightForRowAtIndexPath:, and the latter method only be called once (per row per section) before tableView:cellForRowAtIndexPath:, so you have to [tableview reloadData] if want to adjust the UITableViewCell height.
Maybe your can do something in these delegate methods
// The willBegin/didEnd methods are called whenever the 'editing' property is automatically changed by the table (allowing insert/delete/move). This is done by a swipe activating a single row
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;
To your question, you could reset the properties of the text control at - (void)layoutSubviews in your custom UITableViewCell.
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat theWidth = self.frame.size.height; //changed when entering the edit style
...
}
Hope these will help you.
I want a table view with only cels, and when you click on a cell it should expand and show more info of the clicked cell.
I've seen quite some topics on this, but the most of them are linking to Table View Animations and Gestures on the apple developer page. Which does it in a different way. They use header sections, but I want to use the cell which is expandable for layout reasons.
I already tried several things mainly with
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (isSearching && indexPath.row == selectedIndex) {
return 110;
}
else {
return rowHeight;
}
When I Click on the cell, the cell is expanded but the info in that cell stays the same. Also the heigth of the cell when expanded should be related to the amount of text in the details.
Thnx!
You can achieve this through the use of custom cells. Create two custom cells, one for the normal row and other for the expanded row. When the user touches a particular cell, you can record it's indexPath and reload the tableView. While reloading you can change the height of this selected row using the code that you've just posted(increasing the height of only the selected cell). This would give an effect of expanding cell.