I have an UICollectionView and a custom UICollectionViewCell to display my content, which should be refreshed every second.
I am going to invoke reloadData method to reload the whole collection view to fit my needs.
But, but, My collection view cell blinks every time I reload data.
It seems like the image below. Two seconds of my app. The first second is OK. But second second, the collection view display reused cell first (yellow area) and then display the correct cell(configured cell) finally. Which looks like a blink.
It seems like a cell-reuse issue. CollectionView displays cells without completely finish configure it. How could I fix it?
My cellForItemAtIndexPath: method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
YKDownloadAnimeCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[YKDownloadAnimeCollectionCell description] forIndexPath:indexPath];
YKAnimeDownloadTask *animeDownloadTask = [[YKVideoDownloadTaskManager shared] animeDownloadTasks][indexPath.row];
[cell setUpWithDownloadAnime:animeDownloadTask editing:self.vc.isEditing];
cell.delegate = self;
if (self.vc.isEditing) {
CABasicAnimation *imageViewAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
imageViewAnimation.fromValue = #(-M_PI/64);
imageViewAnimation.toValue = #(M_PI/128);
imageViewAnimation.duration = 0.1;
imageViewAnimation.repeatCount = NSUIntegerMax;
imageViewAnimation.autoreverses = YES;
[cell.coverImageView.layer addAnimation:imageViewAnimation forKey:#"SpringboardShake"];
CABasicAnimation *deleteBtnAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
deleteBtnAnimation.fromValue = #(-M_PI/32);
deleteBtnAnimation.toValue = #(M_PI/32);
deleteBtnAnimation.duration = 0.1;
deleteBtnAnimation.repeatCount = NSUIntegerMax;
deleteBtnAnimation.autoreverses = YES;
[cell.deleteBtn.layer addAnimation:deleteBtnAnimation forKey:#"SpringboardShake1"];
} else {
[cell.coverImageView.layer removeAllAnimations];
[cell.deleteBtn.layer removeAllAnimations];
}
return cell;
}
Well I found the answer though it's been so long from the question.
Don't use reloadData() nor reloadItems().
Use collectionView.reloadSections(IndexSet(integer: yourSectionIndex)). The animation will happen smoothly.
self.collectionView.reloadItems(at: self.collectionView.indexPathsForVisibleItems)
Helped for me.
Related
This is my first time working with UICollectionView.
I've got everything set up and laid out as it should be (as far as I know). I've had a little bit of difficulty with the way the dequeue function works for the UICollectionView, but I think I've gotten past that. It was tricky setting up my custom cell classes when I didn't know if initWithFrame would be called or prepareForReuse.
I'm pretty sure the problem lies within the prepareForReuse function, but where is the question.
What happens is, the cells will apparently randomly draw at the top-left of the collection view and some cells will not be where they belong in the grid. (see image attached)
When bouncing, scrolling, and zooming (so as to cause reuse to occur), the problem happens. Randomly a slide will appear in the top left, and other slides will randomly disappear from the grid.
( I need more rep to post an image. Ugh. :| If you can help me, I'll email you the image. bmantzey#mac.com )
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
Slide* thisSlide = [_presentation.slidesInEffect objectAtIndex:indexPath.row];
[BuilderSlide prepareWithSlide:thisSlide];
BuilderSlide* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"PlainSlide" forIndexPath:indexPath];
return cell;}
I'm using a static method to set the Slide object, which contains the data necessary to either prepare the asynchronous download or retrieve the image from disk cache.
It's simply:
+(void)prepareWithSlide:(Slide*)slide{
if(s_slide)
[s_slide release];
s_slide = [slide retain];}
I'm not sure if it's a big no-no to do this but in my custom Cell class, I'm calling prepareForReuse in the initWithFrame block because I need that setup code to be the same:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self)
{
[self prepareForReuse];
}
return self;}
Here's the prepareForReuse function:
-(void)prepareForReuse{
CGSize size = [SpringboardLayout currentSlideSize];
[self setFrame:CGRectMake(0, 0, size.width, size.height)];
self.size = size;
// First remove any previous view, so as not to stack them.
if(_builderSlideView)
{
if(_builderSlideView.slide.slideID == s_slide.slideID)
return;
[_builderSlideView release];
}
for(UIView* aView in self.contentView.subviews)
{
if([aView isKindOfClass:[BuilderSlideView class]])
{
[aView removeFromSuperview];
break;
}
}
// Then setup the new view.
_builderSlideView = [[BuilderSlideView alloc] initWithSlide:s_slide];
self.builderCellView = _builderSlideView;
[s_slide release];
s_slide = nil;
[self.contentView addSubview:_builderSlideView];
if([SlideCache isImageCached:_builderSlideView.slide.slideID forPresentation:_builderSlideView.slide.presentationID asThumbnail:YES])
{
[_builderSlideView loadImageFromCache];
}
else
{
[_builderSlideView loadView];
}}
Finally, when the slide image has been downloaded, a Notification is posted (I plan on changing this to a delegate call). The notification simply reloads the cell that has received an update. Here's the notification code:
-(void)didLoadBuilderCellView:(NSNotification*)note{
BuilderCellView* cellView = [[note userInfo] objectForKey:#"cell"];
BuilderSlideView* slideView = (BuilderSlideView*)cellView;
NSIndexPath* indexPath = [self indexPathForSlide:slideView.slide];
if(indexPath)
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];}
Note that the slide objects exist in the model.
Any ideas as to what may be causing this problem? Thanks in advance!
The problem that was causing the cells to draw top left and/or disappear was caused by infinite recursion on a background thread. Plain and simply, I wasn't implementing the lazy loading correctly and safely. To solve this problem, I went back to the drawing board and tried again. Proper implementation of a lazy loading algorithm did the trick.
I am new to IOS.
I have used collection view. I am using Prototype cell in it. I have not used Auto-Layout in my app.
I need to change frame of buttons and view in cellForItemAtIndexPath. But when I first scroll to move to next cell it not reflecting first time. I can only see blank with out any buttons or view. I have checked all the frames are correctly updating. Still I can not see any thing.
Though if I again go to previous call and come back it will seen properly.
Please find below code of cellForItemAtIndexPath
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
Feed *feed;
NSMutableAttributedString * string;
feed = [arrTop objectAtIndex:indexPath.row];
ShuffleCell *myCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"ShuffleCell"
forIndexPath:indexPath];
myCell.index = indexPath;
myCell.delegate = self;
// Below frame changes are not working
myCell.vwBack.frame = collect.frame;
myCell.imgShare.frame = CGRectMake(myCell.imgShare.frame.origin.x, myCell.imgShare.frame.origin.y, self.view.frame.size.width,self.view.frame.size.width );
myCell.lblText.center = CGPointMake(myCell.imgShare.frame.size.width / 2, myCell.imgShare.frame.size.height / 2);
myCell.lblText.frame = CGRectMake(20
, myCell.imgShare.frame.origin.y + 20, self.view.frame.size.width - 20,self.view.frame.size.width -20);
int totHeight = (collect.frame.size.height - self.view.frame.size.width);
myCell.vwBtn.frame = CGRectMake(myCell.vwBtn.frame.origin.x, collect.frame.size.height -totHeight, myCell.vwBtn.frame.size.width, totHeight);
[myCell.vwBtn setFrame:CGRectMake(myCell.vwBtn.frame.origin.x, collect.frame.size.height -totHeight, myCell.vwBtn.frame.size.width, totHeight)];
myCell.btnFavoriteBack.frame = CGRectMake(myCell.btnFavoriteBack.frame.origin.x, 0, myCell.btnFavoriteBack.frame.size.width, totHeight/3);
[myCell.btnFavoriteBack setFrame:CGRectMake(myCell.btnFavoriteBack.frame.origin.x, 0, myCell.btnFavoriteBack.frame.size.width, totHeight/3)];
myCell.btnShare.frame = CGRectMake(myCell.btnShare.frame.origin.x, (totHeight/3), myCell.btnShare.frame.size.width, totHeight/3);
myCell.btnReport.frame = CGRectMake(myCell.btnReport.frame.origin.x, (totHeight/3)*2, myCell.btnReport.frame.size.width, totHeight/3);
myCell.vwLikeBtn.frame = CGRectMake(self.view.frame.size.width - 82, (totHeight/3 - 32)/2, 82,32);
NSLog(#"Y :- %f",myCell.vwBtn.frame.size.height);
return myCell;
I have also attached screen shot of same.
Thanks for Help.
Adding this line should fix the problem. Let the subviews of cell translate their frames to constraints.
cell.label.translatesAutoresizingMaskIntoConstraints = YES;
I have a tableview in my view-controller and each tableview cell is associated with an audio file. I'm using custom tableview cells that have a UIView inside. The UIView for each cell has a unique UIBezierPath that is stroked in blue. When the cell is tapped, didSelectRowForIndexPath initiates playing the audio file and the blue UIBezierPath is stroked over with a red UIBezierPath in relation to the progress of the audio file.
float progress = playTime/duration;
self.redWave.strokeEnd = self.progress;
This is working just fine. What I noticed is that when I tap the first cell, the audio will start playing and the red path will begin to get stroked - which is suppose to happen, but if I scroll down while this is happening, the 5th cell down from the one I tapped has the same UIBezierPath and it is also stroking the red path over the blue path! Its clear that the cell is being reused. Also, this isn't just happening when I tap a cell - the UIBezierPath's of the first 5 cells are replicated in the next set of 5 cells by default when my tableview loads. I don't know how to get around this. Any ideas?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* identifier = #"audioTableCell";
OSAudioTableCell *cell = [self.audioTable dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
{
cell = [[OSAudioTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: identifier];
}
//Pull data from NSFetchResultsController
Recording * recording = [self.managedDocument.frc objectAtIndexPath:indexPath];
cell.waveView.minAmpl = [recording.minAmpl floatValue];
//UIBezierPath Data
NSData * pathData = recording.waveData;
//set path for UIView (waveView is custom UIView class)
cell.waveView.path = [NSKeyedUnarchiver unarchiveObjectWithData:pathData];
[cell setImageSize:cell.waveView.bounds.size];
cell.tag = indexPath.row;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//tableCell textfield
NSString * track = #"Track";
NSInteger row = indexPath.row;
row = [self.managedDocument.frc.fetchedObjects count] - row;
track = [track stringByAppendingFormat:#" %li",(long)row];
cell.trackLabel.text = track;
[cell.textField setFont:[UIFont fontWithName:#"Raleway-SemiBold" size:12]];
cell.textField.delegate = self;
if([recording.trackTitle isEqualToString:#""])
{
cell.textField.text = track;
}
else
{
cell.textField.text = recording.trackTitle;
}
UIColor * circleColor = [UIColor colorWithRed:107/255.0f green:212/255.0f blue:231/255.0f alpha:1.0f];
cell.trackIcon.backgroundColor = circleColor;
[cell.trackIcon.layer setCornerRadius:cell.trackIcon.bounds.size.width/2];
cell.trackIcon.layer.masksToBounds = YES;
return cell;
}
didSelectRowAtIndexPath calls the following method in my tableViewCell class:
-(void) rowSelected2: (NSURL*) url
{
self.audioPlayer = [OSTablePlayerController getInstance];
NSData *data=[NSData dataWithContentsOfURL:url];
[self.audioPlayer playAudio:data];
self.audioPlayer.isPlayerPlaying = YES;
self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:#selector(updateProgress:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
Table views are designed to reuse the rows. It's a feature that can improve the performance of your app, especially if your table view has a lot of elements in it.
Since table views reuse their rows, you need to manually clear your rows before they are recycled. If you implemented your cells by creating a UITableViewCell subclass, then you can override its
- (void)prepareForReuse
method. This gets called each time a cell is about to be reused, and it is the perfect location for doing any clean up that might need to happen.
Many apps that play media in tableview cells (e.g. tumblr) stop the playback as the cells are scrolled away (probably using prepareForReuse).
If you want to continue the playback, you'll need to keep all of the state associated with that playback outside of the table cell (in your model) including the playback progress. Your custom cell should be prepared to indicate playback progress starting at any point (from 0-99%). When a cell comes into view (in your datasource cellForRowAtIndexPath), you'll be required to check your model to see if that cell's media is playing and have it draw the current progress (hopefully it has a method setProgress:(float)progress that changes a progress property and tells that bezier drawing view to setNeedsDisplay).
This also means that if there's a timer set to check the media playback progress, it's target cannot be the cell, but rather the view controller that maintains the model.
I am trying to create custom tiled layout using UICollectionView.
It renders perfectly as desired in simulator once I run my app.
But the moment I scroll the view and bring it back all the cell's frame changes and the cells get overlapped, leaving spaces, randomly.
I am not able to solve this issue past 2 days.
Here goes the code from my custom layout class.
-(void)prepareLayout{
[self createCellSizeArray];//cellSizeArray holds cell sizes for all the cells(calculated statically)
[self createAttributeArrayOfAll];//attributeArrayOfAll holds attributes for all the cells and also calculates their frames using cellSizeArray
}
-(CGSize)collectionViewContentSize{
return CGSizeMake(768, 1500);//The size is static to check for scrolling, is this creating problems?
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
return layoutAttributes;
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray array];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray addObject:attribute];
}
}
return attArray;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
return YES;
}
Please help, Thanks in advance.
Edit:
In my [self createAttributeArrayOfAll]; I have these lines of code
CGRect frame = CGRectMake(_startNewRowPoint.x, _startNewRowPoint.y, cellSize.width, cellSize.height);
UICollectionViewLayoutAttributes * attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
attribute.alpha = 1.0;
attribute.frame = frame;
[attributeArrayOfAll addObject:attribute];
While I modified layoutAttributesForItemAtIndexPath:, to look something like this
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewLayoutAttributes * layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
layoutAttributes.frame = ((UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:indexPath.item]).frame;
return layoutAttributes;
}
Moreover, the method layoutAttributesForItemAtIndexPath: never gets called implicitly. I even tried this:
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *attArray =[NSMutableArray arrayWithCapacity:attributeArrayOfAll.count];
for (NSInteger i =0; i< attributeArrayOfAll.count; i++) {
UICollectionViewLayoutAttributes * attribute = (UICollectionViewLayoutAttributes*)[attributeArrayOfAll objectAtIndex:i];
if(CGRectIntersectsRect(rect, attribute.frame)){
[attArray insertObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]] atIndex:i];
}
}
return attArray;
}
But still the result is the same distorted set of cells on scrolling.
I worked with 5 cells, first time it renders correctly, on scrolling away and then bringing it back in visible rect it gets distorted, if i scroll away again and bring it back in visible rect it renders correctly. However, when I do this with around 400 cells, once i scroll it never renders correctly. Even on reloading collection view, The cells gets distort. Please help.
Your layoutAttributesForItemAtIndexPath: method is not setting any properties of the layoutAttributes object before returning it. It needs to set frame (or center and size).
So finally managed a workaround!!! dequeue each cell with an unique Cell Identifier in cellForRow:
[self.summaryView registerClass:[BFSSummaryViewCell class] forCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row]];
UICollectionViewCell *collectionCell = [collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:#"%#%d",CellIdentifier,indexPath.row] forIndexPath:indexPath];
These two lines inside cellForRow worked for me, however with my collection view having around 1000 cells it increases the size of my application considerably. Lets hope apple fixes this bug asap.
We are trying to set up a UICollectionView with a custom layout. The content of each CollectionViewCell will be an image. Over all there will be several thousand images and about 140-150 being visible at one certain time. On an action event potentially all cells will be reorganized in position and size. The goal is to animate all the moving events currently using the performBatchUpdates method. This causes a huge delay time before everything gets animated.
This far we found out that internally the method layoutAttributesForItemAtIndexPath is called for every single cell (several thousand in total). Additionally, the method cellForItemAtIndexPath is called for more cells than can actually be displayed on the screen.
Are there any possibilities to enhance the performance of the animation?
The default UICollectionViewFlowLayout can't really offer the kind of design we want to realize in the app. Here's some of our code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance]; //Singleton holding some global information
NSArray *plistArray = dm.plistArray; //Array containing the contents of the cells
NSDictionary *dic = plistArray[[indexPath item]];
RPCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
cell.label.text = [NSString stringWithFormat:#"%#",dic[#"name"]];
cell.layer.borderColor = nil;
cell.layer.borderWidth = 0.0f;
[cell loadAndSetImageInBackgroundWithLocalFilePath:dic[#"path"]]; //custom method realizing asynchronous loading of the image inside of each cell
return cell;
}
The layoutAttributesForElementsInRect iterates over all elements setting layoutAttributes for allthe elements within the rect. The for-statement breaks on the first cell being past the borders defined by the bottom-right corner of the rect:
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray* attributes = [NSMutableArray array];
RPDataModel *dm = [RPDataModel sharedInstance];
for (int i = 0; i < dm.cellCount; i++) {
CGRect cellRect = [self.rp getCell:i]; //self.rp = custom object offering methods to get information about cells; the getCell method returns the rect of a single cell
if (CGRectIntersectsRect(rect, cellRect)) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:[dm.relevanceArray[i][#"product"] intValue] inSection:0];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attribute.size = cellRect.size;
attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
[attributes addObject:attribute];
} else if (cellRect.origin.x > rect.origin.x + rect.size.width && cellRect.origin.y > rect.origin.y + rect.size.height) {
break;
}
}
return attributes;
}
On Layout changes the results are pretty much the same no matter if the number of cells being defined in the layoutAttributesForElementsInRect is limited or not .. Either the system gets the layout attributes for all cells in there if it isn't limited or it calls the layoutAttributesForElementAtIndexPath method for all the missing cells if it is limited. Overall the attributes for every single cell is being used somehow.
-(UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
RPDataModel *dm = [RPDataModel sharedInstance];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect cellRect = [self.rp getCell:[dm.indexDictionary[#(indexPath.item)] intValue]];
attribute.size = cellRect.size;
attribute.center = CGPointMake(cellRect.origin.x + attribute.size.width / 2, cellRect.origin.y + attribute.size.height / 2);
return attribute;
}
Without seeing code, my guess is that your layoutAttributesForElementsInRect method is iterating through all of the items in your collection, and that is, in turn, what is causing the other methods to get over-called. layoutAttributesForElementsInRect is giving you a hint - that is, the CGRect it is passing to you - about which items you need to call layoutAttributesForItemAtIndexPath for, in terms of what is on the screen.
So that may be part of the performance issue - that is, tuning your custom layout so it is being smart about which items it is updating.
The other issues pertain to animation performance in general. One thing to watch out for is if there is any sort of compositing going on - make sure your images are opaque. Another thing is if you're using shadows on your image, those can be expensive to animate. One way to improve the animation performance of shadows is set the shadowPath value when the image is resized - if you do have shadows, let me know and post some code for doing that.
This appears to be caused by trying to add cells to sections which have header views but no cells already in them.
The error message is monumentally unhelpful, and it took several hours of effort to trace it down.