I have a UICollectionView to show some components.
Its vertical scrolling and with only 1 column.
I am adding 2 same objects but with different data to collectionView's datasource at different sections.
And in my cell class my initialisation and update methods are as follow
- (void)commonInitializer {
self.backgroundColor = kVTColorWhite;
self.imageView = [[VTUIKit imageSquareXL2] addToSuperview:self];
self.imageView.f_x = kVTPadding;
self.imageView.backgroundColor = kVTColorBackground;
self.imageView.f_width = [VTAdSpaceDisplayCell cellSize].width - 2*kVTPadding;
self.imageView.f_height = [VTUIKit screenWidth]/3.125;
self.imageView.layer.cornerRadius = 2.0;
self.imageView.layer.masksToBounds = YES;
}
- (void)updateWithModel:(id)model{
self.model = model;
if (self.model.image == nil) {
[self updateEmptyCells];
}
else{
[self.imageView setImageWithUrl:self.model.image optimizedForSize:[self class] cellSize] withPlaceholder:[UIImage imageNamed:#"Placeholder-Image"]];
}
}
But collectionView's didSelectItemAtIndexPath for this cell is being called sometimes and sometimes not, its very weird.
I have also set collectionView's delegate to current class and I am also not adding any gesture recogniser to cell or collectionView.
But still the behaviour is strange and very random, I cant even get a particular scenario to debug.
Any help would be much appreciated.
Thanks
The actual problem is nowhere related to UIImageView or UICollectionViewCell.
Earlier I was using [self.collectionView reloadSections:[INDEX]] for reloading collectionView's sections and that was creating some problems and I dont know why.
But, I changed them to [self.collectionView reloadData] and the issue is gone.
Very strange and weird reason for issue to appear and I have no explanation even though I searched for it.
Anyways, I got my issue resolved so I am posting my solution as an answer if that helps anyone in future.
Thanks
Related
I know this question has been asked before. But no person on the internet had a working and sufficient answer.
EDIT Obviously people don't read questions anymore, on SO. So I'm trying to clarify: I want to remove the SEPARATOR. The separator is neither the space above the section, nor the tableViewHeader or tableViewFooterView. It is only the thin line above (fully from left to right).
I have a grouped UITableView (I don't want to use a plain styled for many other reasons, take it as it is) which has multiple groups.
The first section should not have the separator line on top. Setting the separator style of the tableView is not an option, because I do need the other separators.
Setting the tableViews tableFooterView is something I often read, but it never worked.
I used the tableView with static content before and I was able to remove the separator in -[UITableViewController viewDidLoad] using this:
- (void)viewDidLoad {
[[[self headerTableCell] valueForKey:#"_topSeparatorView"] removeFromSuperView];
}
Since I now had to change the tableView to a dynamic one, the IBOutlet property won't work anymore (obviously).
So I tried everything, -[id tableView:willDisplayCell:atIndexPath:], -[UITableViewCell initWithStyle:reuseIdentifier:, prepareForReuse, awakeFromNib] and some others.
In any case, this separator is nil. So I need a method that gets called when the complete view hierarchy of the cell is setup.
what i get from your situation you have a grouped UITableView you want the first section without separator and you want to keep the separator in the other sections so
remove the separator from the whole tableview from the attributes inspector make Separator : None
create custom UITableviewCell in storyboard for other sections and add View at the end of it with height 1 and width the whole screen (like default separator)
it's maybe not the best idea but this will allow you to have the first section without separator
I faced a similar problem, wanted to remove the last line of the section in grouped table view, I am calling following method in view will appear and on every table reload. This is not the exact answer but problem can be solved by just changing y value of dummy view.
+(void)removeLastSectionSeparatorForTableView:(UITableView *)tableView
{
UIView *oldSeparatorView = [tableView viewWithTag:kTagDummySectionSeparator];
if (oldSeparatorView != nil)
{
[oldSeparatorView removeFromSuperview];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(),
^{
UIView *view = [[UIView alloc] init];
view.tag = kTagDummySectionSeparator;
view.backgroundColor = [UIColor colorWithRed:239.0/255 green:239.0/255 blue:244.0/255 alpha:1.0];//Group table background color
view.frame = CGRectMake(0,
tableView.contentSize.height-40,
tableView.bounds.size.width,
2);
[tableView addSubview:view];
});
}
Maybe this problem is the same as mine before.
Finally, my way to solve this problem: set table view delegate's method (CGFloat)tableView:(UITableView *)tableView heightForHeaderAtSection:(NSInteger)section, then return CGFLOAT_MIN;
add this override function in your Custom Cell Class
override func layoutSubviews() {
super.layoutSubviews()
for subview in subviews where (subview != contentView && abs(subview.frame.width - frame.width) <= 0.1 && subview.frame.height < 2) {
subview.removeFromSuperview()
}
}
tldr; Auto constrains appear to break on push segue and return to view for custom cells
Edit: I have provided a github example project that shows off the error that occurs
https://github.com/Matthew-Kempson/TableViewExample.git
I am creating an app which requires the title label of the custom UITableCell to allow for varying lines dependent on the length of the post title. The cells load into the view correctly but if I press on a cell to load the post in a push segue to a view containing a WKWebView you can see, as shown in the screen shot, the cells move immediately to incorrect positions. This is also viewed when loading the view back through the back button of the UINavigationController.
In this particular example I pressed on the very end cell, with the title "Two buddies I took a picture of in Paris", and everything is loaded correctly. Then as shown in the next screenshot the cells all move upwards for unknown reasons in the background of loading the second view. Then when I load the view back you can see the screen has shifted upwards slightly and I cannot actually scroll any lower than is shown. This appears to be random as with other tests when the view loads back there is white space under the bottom cell that does not disappear.
I have also included a picture containing the constraints that the cells has.
Images (I need more reputation to provide images in this question apparently so they are in this imgur album): http://imgur.com/a/gY87E
My code:
Method in custom cell to allow the cell to resize the view correctly when rotating:
override func layoutSubviews() {
super.layoutSubviews()
self.contentView.layoutIfNeeded()
// Update the label constaints
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.width
self.detailsLabel.preferredMaxLayoutWidth = self.detailsLabel.frame.width
}
Code in tableview
override func viewDidLoad() {
super.viewDidLoad()
// Create and register the custom cell
self.tableView.estimatedRowHeight = 56
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Code to create the cell
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
if let cell = tableView.dequeueReusableCellWithIdentifier("LinkCell", forIndexPath: indexPath) as? LinkTableViewCell {
// Retrieve the post and set details
let link: Link = self.linksArray.objectAtIndex(indexPath.row) as Link
cell.titleLabel.text = link.title
cell.scoreLabel.text = "\(link.score)"
cell.detailsLabel.text = link.stringCreatedTimeIntervalSinceNow() + " ago by " + link.author + " to /r/" + link.subreddit
return cell
}
return nil
}
If you require any more code or information please ask and I shall provide what is necessary
Thanks for your help!
This bug is caused by having no tableView:estimatedHeightForRowAtIndexPath: method. It's an optional part of the UITableViewDelegate protocol.
This isn't how it's supposed to work. Apple's documentation says:
Providing an estimate the height of rows can improve the user experience when loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights and so lead to a longer load time. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
So this method is supposed to be optional. You'd think if you skipped it, it would fall back on the accurate tableView:heightForRowAtIndexPath:, right? But if you skip it on iOS 8, you'll get this behaviour.
What seems to be happening? I have no internal knowledge, but it looks like if you do not implement this method, the UITableView will treat that as an estimated row height of 0. It will compensate for this somewhat (and, at least in some cases, complain in the log), but you'll still see an incorrect size. This is quite obviously a bug in UITableView. You see this bug in some of Apple's apps, including something as basic as Settings.
So how do you fix it? Provide the method! Implement tableView: estimatedHeightForRowAtIndexPath:. If you don't have a better (and fast) estimate, just return UITableViewAutomaticDimension. That will fix this bug completely.
Like this:
- (CGFloat)tableView:(UITableView *)tableView
estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
There are potential side effects. You're providing a very rough estimate. If you see consequences from this (possibly cells shifting size as you scroll), you can try to return a more accurate estimate. (Remember, though: estimate.)
That said, this method is not supposed to return a perfect size, just a good enough size. Speed is more important than accuracy. And while I spotted a few scrolling glitches in the Simulator there were none in any of my apps on the actual device, either the iPhone or iPad. (I actually tried writing a more accurate estimate. But it's hard to balance speed and accuracy, and there was simply no observable difference in any of my apps. They all worked exactly as well as just returning UITableViewAutomaticDimension, which was simpler and was enough to fix the bug.)
So I suggest you do not try to do more unless more is required. Doing more if it is not required is more likely to cause bugs than fix them. You could end up returning 0 in some cases, and depending on when you return it that could lead to the original problem reappearing.
The reason Kai's answer above appears to work is that it implements tableView:estimatedHeightForRowAtIndexPath: and thus avoids the assumption of 0. And it does not return 0 when the view is disappearing. That said, Kai's answer is overly complicated, slow, and no more accurate than just returning UITableViewAutomaticDimension. (But, again, thanks Kai. I'd never have figured this out if I hadn't seen your answer and been inspired to pull it apart and figure out why it works.)]
Note that you may also need to force layout of the cell. You'd think iOS would do this automatically when you return the cell, but it doesn't always. (I will edit this once I investigate a bit more to figure out when you need to do this.)
If you need to do this, use this code before return cell;:
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
The problem of this behavior is when you push a segue the tableView will call the estimatedHeightForRowAtIndexPath for the visible cells and reset the cell height to a default value. This happens after the viewWillDisappear call. If you come back to TableView all the visible cells are messed up..
I solved this problem with a estimatedCellHeightCache. I simply add this code snipped to the cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
// put estimated cell height in cache if needed
if (![self isEstimatedRowHeightInCache:indexPath]) {
CGSize cellSize = [cell systemLayoutSizeFittingSize:CGSizeMake(self.view.frame.size.width, 0) withHorizontalFittingPriority:1000.0 verticalFittingPriority:50.0];
[self putEstimatedCellHeightToCache:indexPath height:cellSize.height];
}
...
}
Now you have to implement the estimatedHeightForRowAtIndexPath as following:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self getEstimatedCellHeightFromCache:indexPath defaultHeight:41.5];
}
Configure the Cache
Add this property to your .h file:
#property NSMutableDictionary *estimatedRowHeightCache;
Implement methods to put/get/reset.. the cache:
#pragma mark - estimated height cache methods
// put height to cache
- (void) putEstimatedCellHeightToCache:(NSIndexPath *) indexPath height:(CGFloat) height {
[self initEstimatedRowHeightCacheIfNeeded];
[self.estimatedRowHeightCache setValue:[[NSNumber alloc] initWithFloat:height] forKey:[NSString stringWithFormat:#"%d", indexPath.row]];
}
// get height from cache
- (CGFloat) getEstimatedCellHeightFromCache:(NSIndexPath *) indexPath defaultHeight:(CGFloat) defaultHeight {
[self initEstimatedRowHeightCacheIfNeeded];
NSNumber *estimatedHeight = [self.estimatedRowHeightCache valueForKey:[NSString stringWithFormat:#"%d", indexPath.row]];
if (estimatedHeight != nil) {
//NSLog(#"cached: %f", [estimatedHeight floatValue]);
return [estimatedHeight floatValue];
}
//NSLog(#"not cached: %f", defaultHeight);
return defaultHeight;
}
// check if height is on cache
- (BOOL) isEstimatedRowHeightInCache:(NSIndexPath *) indexPath {
if ([self getEstimatedCellHeightFromCache:indexPath defaultHeight:0] > 0) {
return YES;
}
return NO;
}
// init cache
-(void) initEstimatedRowHeightCacheIfNeeded {
if (self.estimatedRowHeightCache == nil) {
self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
}
}
// custom [self.tableView reloadData]
-(void) tableViewReloadData {
// clear cache on reload
self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init];
[self.tableView reloadData];
}
I had the exact same problem. The table view had several different cell classes, each of which was a different height. Moreover, one of the cells classes had to show additional text, meaning further variation.
Scrolling was perfect in most situations. However, the same problem described in the question manifested. That was, having selected a table cell and presented another view controller, on return to the original table view, the upwards scrolling was extremely jerky.
The first line of investigation was to consider why data was being reloaded at all. Having experimented, I can confirm that on return to the table view, data is reloaded, albeit not using reloadData.
See my comment ios 8 tableview reloads automatically when view appears after pop
With no mechanism to deactivate this behaviour, the next line of approach was to investigate the jerky scrolling.
I came to the conclusion that the estimates returned by estimatedHeightForRowAtIndexPath are an estimated precalculation. Log to console out the estimates and you'll see that the delegate method is queried for every row when the table view first appears. That's before any scrolling.
I quickly discovered that some of the height estimate logic in my code was badly wrong. Resolving this fixed the worst of the jarring.
To achieve perfect scrolling, I took a slightly different approach to the answers above. The heights were cached, but the values used were from the actual heights that would have been captured as the user scrolls downwards:
var myRowHeightEstimateCache = [String:CGFloat]()
To store:
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
myRowHeightEstimateCache["\(indexPath.row)"] = CGRectGetHeight(cell.frame)
}
Using from the cache:
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if let height = myRowHeightEstimateCache["\(indexPath.row)"]
{
return height
}
else
{
// Not in cache
... try to figure out estimate
}
Note that in the method above, you will need to return some estimate, as that method will of course be called before didEndDisplayingCell.
My guess is that there is some sort of Apple bug underneath all of this. That's why this issue only manifests in an exit scenario.
Bottom line is that this solution is very similar to those above. However, I avoid any tricky calculations and make use of the UITableViewAutomaticDimension behaviour to just cache the actual row heights displayed using didEndDisplayingCell.
TLDR: work around what's most likely a UIKit defect by caching the actual row heights. Then query your cache as the first option in the estimation method.
Well, until it works, you can delete those two line:
self.tableView.estimatedRowHeight = 45
self.tableView.rowHeight = UITableViewAutomaticDimension
And add this method to your viewController:
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as TableViewCell
cell.cellLabel.text = self.tableArray[indexPath.row]
//Leading space to container margin constraint: 0, Trailling space to container margin constraint: 0
let width = tableView.frame.size.width - 0
let size = cell.cellLabel.sizeThatFits(CGSizeMake(width, CGFloat(FLT_MAX)))
//Top space to container margin constraint: 0, Bottom space to container margin constraint: 0, cell line: 1
let height = size.height + 1
return (height <= 45) ? 45 : height
}
It worked without any other changes in your test project.
If you have set tableView's estimatedRowHeight property.
tableView.estimatedRowHeight = 100;
Then comment it.
// tableView.estimatedRowHeight = 100;
It solved the bug which occurs in iOS8.1 for me.
If you really want to keep it,then you could force tableView to reloadData before pushing.
[self.tableView reloadData];
[self.navigationController pushViewController:vc animated:YES];
or do it in viewWillDisappear:.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.tableView reloadData];
}
Hope it helps.
In xcode 6 final for me the workaround does not work. I am using custom cells and dequeing a cell in heightForCell leads to infinity loop. As dequeing a cell calls heightForCell.
And still the bug seems to be present.
If none of the above worked for you (as it happened to me) just check the estimatedRowHeight property from the table view is kind of accurate. I checked I was using 50 pixels when it was actually closer to 150 pixels. Updating this value fixed the issue!
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = tableViewEstimatedRowHeight // This should be accurate.
I have a UIScrollview with a UICollectionview in it which has multiple custom UICollectionViewCells.
What a I want is to stretch a UIView over multiple cells.
I tried to use: cell.clipToBounds = NO; which is working but when I scroll to left and scroll back the expanded part of the UIView is being cut off again.
I guess it has something to do with dequeuing of the cells which aren't used. Can I assign the expanded part to the new cell so that it won't get deleted or sth?
I had a former problem where Cells got duplicated therefor I had overwritten the method prepareForReuse of my custom UICollectionViewCell-Class.
-(void)prepareForReuse
{
for(id aView in [self.contentView subviews])
{
if ([aView isKindOfClass:[MyCustomUIView class]])
{
[aView removeFromSuperview];
}
}
}
Any advice and help will be thankfully appreciated.
If your cell.clipsToBounds = NO is working, but only the first time, I agree that the problem probably has to do with cell reuse. If you are overriding -prepareForReuse, you probably want to set self.clipsToBounds = NO in there. Then, in -collectionView:cellForItemAtIndexPath:, set clipsToBounds to YES or NO every time as needed.
I am using Storyboard to create the UICollectionViewController - CollectionView - Cell(my own DasboardsViewCell) and my customer view (DashBoardView) inside cell. I wired up everything correctly and everything seems to work except when I scroll up and down. I will explain what my understanding is after I debug.
Also I have 2 views inside my DashBoardView custom view which i used "one" as main primary and other as FlipView (e.g. when user taps on cell). Since everything is wired from storyboard I dont have to registerClass for reusing.
1) In my DashboardCustomView (who has 2 other views in it as above) i do this
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_isPrimary = YES;
//to init my 2 child views and insert to main custom view
[self initSubViewsWithInsert];
}
return self;
}
2) In my DashBoardViewCell class I do this
#synthesize dashBoardView = _dashBoardView;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.backgroundColor = [UIColor colorWithWhite:0.75f alpha:0.6f];
self.layer.borderColor = [UIColor blackColor].CGColor;
self.layer.borderWidth = 2.0f;
self.layer.cornerRadius = 7.0f;
}
return self;
}
// If I dont do this below I get overlapped images and data on cell when scrolling up and down
- (void)prepareForReuse
{
self.dashBoardView.mainContainerView = nil;
self.dashBoardView.flipView = nil;
}
3) Now after this I still see the custom view appears out of order and seems somewhat better than not doing "nil" on my views, but the problem is once I nil out my 2 subviews I have no defined way to reinitialize them when my collectionview controller requeus the cell and start adding my content.
i.e. I do like this cellForItemAtIndexPath
if ([cell isKindOfClass:[DashboardsViewCell class]]) {
DashboardsViewCell *dashCell = (DashboardsViewCell*) cell;
Dashboard* tmpDashboard = self.availableDashBoards[indexPath.item];
[dashCell.dashBoardView addDashboardImageViewAtPoint:tmpDashboard.identifier
usingIndexPath:indexPath];
Last line above is adding some image and text on dashboards main view which is self.dashBoardView.mainContainerView and it has already nil.
Can someone help me understand if there is defined way to do this. Also if you think I am looking at wrong problem
I'm not sure what the issue is exactly and with that code it's a bit hard to tell. If you could post the project somewhere it would be easier. Now that many things are storyboarded, it's harder to fix issues just by posting code :(
But are you using auto layout by any chance?
Thanks for your reply - No Im not using auto layout and I am unable to post my code here as it has grown too big now but here is what I did to solve the problem
1) I realized when cells are being refreshed (dequeue) my logic was such that I was actually maintaining state for certain cell (User taps dashboard-B from A,B,C,D etc) with my own internal state thinking that same cell will be applied to same indexPath. i.e.
a)If user taps on cell 2 - I switch to secondary view and maintains that state in my cell.
b)Now if user scrolls up and down then cells get refreshed and I releases my internal view in prepareForResuse
once i know what i was doing wrong, I have to change my approach and move the logic in controller.
I may sound obscure here but I found that I was wrong in my assumption about cell cellForItemAtIndexPath and what it supposed to do. Once I figured that out i was able to fix my design.
What's happening
Currently I have an application that uses two UICollectionViews inside a UITableView. This way I create a Pulse News look like application. My problem with this is that sometimes the 6th and 11th row disappears completely, leaving a blank space where it should be the cell. I wouldn't actually mind, if all the cells were like this (and this way I could assume that I wasn't doing things correctly), but the thing is, is just happening with those specific ones.
My theory
The 6th and 11th rows are the ones that appears when I start scrolling, so by default I am able to see 5 cells, and when I do the first horizontal scrolling the 6th comes up (blank space sometimes).
What I have
The only thing I am doing at the moment is this:
[self.collectionView registerNib:[UINib nibWithNibName:CELL_NIB_NAME bundle:nil] forCellWithReuseIdentifier:CELL_IDENTIFIER];
On the viewDidLoad. And on the creation of the cell:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CELL_IDENTIFIER forIndexPath:indexPath];
NSMutableDictionary *dictionary = [self.DataSource objectAtIndex:[indexPath row]];
[cell buildViewWithDictionary:dictionary withReferenceParent:self.referenceViewController];
return cell;
}
So on my understating nothing fancy going on here. I though there was something wrong on the data source (a dummy JSON file), but sometimes it works ok and the cell shows, so I guess from that part is ok.
So my "question": Does anyone knows what's going on? I don't really like to say that it's a bug from iOS, but I can't think of anything else.
Edit 1.0
The amazing part is that this method
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
Is going from indexPath [0,4] to [0,6] without calculating the [0,5]. First time I actually see this happening in iOS.
Edit 2.0
I have switched the way I am creating the cells, and instead of dequeuing I am using the old way:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CELL_NIB_NAME owner:self options:nil];
MyCell *cell = (MyCell *)[nib objectAtIndex:0];
Still the same sad result.
So, what did work?
1) Subclass UICollectionViewFlowLayout.
2) Set the flowLayout of my UICollectionView to my new subclass.
3) On the init method of the UICollectionViewFlowLayout subclass, set the orientation you want:
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
In my case it is Horizontal.
4) The important part:
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
At this moment, I should theorise a bit, but honestly I don't have a clue.
The above answers didn't work for me, but after downloading the images, I replaced the code
[self.myCollectionView reloadData]
with
[self.myCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
to refresh the collectionview and it shows all cells, you can try it.
None of the solutions given by anyone helped me in my custom layout that we need to have in our app.
Instead, I had to do this: (And yeah, IT WORKS!!!)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
CGSize size = [self collectionViewContentSize];
rect.size.height = size.height*2;
NSArray *atrributes_Super = [super layoutAttributesForElementsInRect:rect];
return atrributes_Super;
}
After all, UICollectionView is just looking for the attributes for the elements to be displayed in your screen's rect.
Rob's tip about the bug helped me. The bug states that if the section insets and cells widths and spacing add up exactly to the width of the screen then the first column sometimes randomly dissappears and reappears for some cells in some places. Rather than subclass or change the cell widths, I changed the section insets for left and right in my storyboard from 6 to 4 and it I haven't seen the problem again.
As I run the same problem suddenly and spent some time figuring out one of possible reasons of cell disappearing during the scroll, I will add my answer as well.
Prerequisites of the problem:
You have a UICollectionView instance
You have a UICollectionViewFlowLayoutSubclass
The problem
Cells disappear from the Collection View after scrolling to the certain point.
The source of the problem is in wrong subclassing of the UICollectionViewFlowLayout.
As it explicitly said in documentation:
Every layout object should implement the following methods:
- collectionViewContentSize
- layoutAttributesForElements(in:)
- layoutAttributesForItem(at:)
- layoutAttributesForSupplementaryView(ofKind:at:) // (if your layout supports -supplementary views)
-layoutAttributesForDecorationView(ofKind:at:) // (if your layout supports decoration views)
- shouldInvalidateLayout(forBoundsChange:)
By relying on UICollectionViewFlowLayout implementation of methods above we miss the fact, that func layoutAttributesForElements(in rect: CGRect) and collectionViewContentSize will generate wrong contentSize (the size that would be correct if all the cells would have itemSize size and the content size would be corresponding. As soon as scroll offsetY will be greater that contentSize height cell will all disappear.
The solution
The solution is in proper UICollectionViewFlowLayout subclassing. Override all the methods that are required to override and everything will work just fine.
In my case (vertical scroll, with cells disappearing in first view), cells were disappearing due to incorrect estimated size. It seems, UICollectionView uses the estimated size to calculate the items to load in first view. I'd set the estimated size too high which was resulting in wrong calculations for number of items to load in first screen.
The moment I made the estimated height bit low, all the cells appeared correctly.
We ran into disappearing cells recently and found that rather than skipping 'hidden' cells we were accidentally inserting 0x0 sized cells. The resulting behavior was very confusing and did not suggest these invisible cells were the issue. We would see the correctly sized cells and layout, but a few of the valid cells would consistently disappear after scrolling off/on screen. I have no idea why intermingling 0 sized cells would cause this behavior, but removing them fixed the problem. This may not be causing your problem, but this may be helpful to devs searching for similar symptoms.
Just ran into an issue where all UICollectionView cells were disappearing on scroll.
This happened because I had declared
extension UICollectionViewLayout {
static let defaultLayout: UICollectionViewLayout {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
return layout
}()
}
... meaning the same layout instance was being used in multiple UICollectionViews. I had meant to make that a computed var. Hope this helps someone who's accidentally using the same layout object in multiple collection views.
What caused the cells to disappear in my case was that the data source was being deallocated prematurely. UICollectionView.dataSource is a weak property, which means that unless you keep a strong reference to it, the object will be deallocated at the end of the scope in which you created. The problem manifested itself with disappearing cells as soon as I tapped on the UICollectionView.
For me this issue seemed to be related with the way i make my collectionview adapt to an open keyboard to prevent content overlaps.
in my observer to respond to KeyboardWillShow i had this:
var userInfo = obj.UserInfo[UIKeyboard.FrameEndUserInfoKey];
if (userInfo is NSValue value)
{
var rect = value.CGRectValue;
var windowOffset = this.Superview.ConvertPointToView(this.Frame.Location, UIApplication.SharedApplication.KeyWindow);
var newHeight = rect.Y - windowOffset.Y;
this._controller.CollectionView.Frame = new CGRect(0, 0, this._controller.CollectionView.Frame.Width, newHeight);
}
After changing it to this:
var userInfo = obj.UserInfo[UIKeyboard.FrameBeginUserInfoKey];
if (userInfo is NSValue value)
{
var rect = value.CGRectValue;
UIEdgeInsets contentInsets = new UIEdgeInsets(0, 0, rect.Height, 0);
this._controller.CollectionView.ContentInset = contentInsets;
this._controller.CollectionView.ScrollIndicatorInsets = contentInsets;
}
The cell disappearance issue completely went away. This is C# from working with xamarin but i hope it helps someone else.
I think this is not a UICollectionView‘s bug, maybe your not return right data in - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect method.
You can see this demo: https://github.com/lqcjdx/YLTagsChooser , all cells can appear when scolling the UICollectionView.