I have a UICollectionView, with custom UICollectionViewCell.
Every cell has a UITextView inside.
The problem: the UICollectionView is very slow, also in the simulator.
-(void)viewDidLoad
{
[self.collectionView registerClass:[AgendaCollectionCell class]
forCellWithReuseIdentifier:agendaCellIdentifier];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
AgendaCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:agendaCellIdentifier forIndexPath:indexPath];
cell.delegate = self;
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
[cell setCurrentDateAtIndex:([indexPath row]) date:currentMonth events:events];
// Return the cell
return cell;
}
I checked the profiler
Every cell makes date-related computations.
As you can see, the loadNibNamed takes too much time to load. Also the UITextView takes too much.
I searched a lot of question here and used some of their answers.(I also cached all instances of NSCalendar). I do not understand why it takes about 435 ms to load. The UICollectionView has 40 cell (it contains the days of a month).
Should I abandon the use of UICollectionView and cast to a custom view by using drawRect?
EDIT
I think that a good point is that suggested in the answer: load the cell by using UNib and not registerClass. I can see a very big performance boost: from 400ms to just 98ms!
Try putting
UINib * nib = [UINib nibWithNibName:#"YourNibName" bundle:[NSBundle mainBundle]];
[self.collectionView registerNib:nib forCellWithReuseIdentifier:#"MyCell"];
into your viewDidLoad and then never create the cells yourself (in the
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForIndexPath:(NSIndexPath *)indexPath;
implementation always call
UICollectionViewCell * cell = [collectionView dequeueCell...]
This way, I think, the collection view will only load the nib once (in your viewDidLoad method and then create copies of it when needed.
Related
Im trying to load UICollectionViewCell from .xib, to the UICollectionView, which is in reusable UIView also in .xib. This UIView will be loaded to more UIViewControllers. I found out, that I need load cell like this:
[self.collectionView registerNib:[UINib nibWithNibName:#"MyCell" bundle:nil] forCellWithReuseIdentifier:#"CELL"];
but it will not load it.
I looked all around net for answer, but I couldn't find this specific case.
There are two different cases,
1) If you're using storyboards and custom cells.
2) Nib and simple UIView(hierarchy of UIViews).
Yours seems to be the second case,
First of all you need to make instance of a UICollectionView(by alloc init methods).
Then register the Nib holding your cell/view using following method.
[self.collectionView registerNib:[UINib nibWithNibName:CELL_IDENTIFIER bundle:nil] forCellWithReuseIdentifier:CELL_IDENTIFIER];
and then you can use that Nib using following method
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell;
cell = (UICollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:CELL_IDENTIFIER
forIndexPath:indexPath];
//Populate your data here.
return cell;
}
Hope this helps.. :)
I have code:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 30;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:REUSE_IDENTIFIER forIndexPath:indexPath];
[[cell subviews]
makeObjectsPerformSelector:#selector(removeFromSuperview)];
[cell addSubview:someView];
return cell;
}
Above code gets executed in viewWillAppear
If I make the above code executed 30 times, the view appearance is really slow.
Due to cell reuse, I need to make a call to removeFromSuperView. Otherwise stale wrong data is displayed on the cell.
I am not able to figure out what could be causing the slowness??
Can you provide hints?
Your appearance is slow because you are iterating through all the subviews within the cell and removing from superview. This is a very expensive process and should not be done in the cellForItemAtIndexPath method of the collectionView data source/ Infact this should never be done. If you need to display relevant content you need to access the instance of the cell and update the properties of the UI elements within the cell.
I'm not using storyboard, everything is done by code..
and when I scroll the UICollectionView.. after it reusing correctly..some cells..
than it happen :
-the cell initWithFrame is being call
-new gray hair appear on my head.
I read other q/a and check maybe it's something with threads but all the reloadData is on the main thread.
any directions ?
I have no idea what's your code, so I'll propose how do I do it:
// somewhere in eg viewDidLoad
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CellId];
And later the delegate:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = cell = [collectionView dequeueReusableCellWithReuseIdentifier:MyCellId forIndexPath:indexPath];
// do something with your cell, set title or anything
return cell;
}
There is another possibility. Your cell, as it's reusable, will have already saved previous properties. So if you did something like this:
if (iCanAddGrayHair) {
[cell.imageView setImage:[UIImage imageNamed:#"hair"]]
}
Then do notice, that new cell will still have this image set! You need to add:
else {
[cell.imageView setImage:nil];
}
To reset it from previous state. You can also override prepareForReuse: method of UICollectionViewCell class to reset the values (don't forget to call super).
After finishing my app, I realized that the memory allocation is incredibly huge.
I think I have isolated the problem to a view which makes use of a UICollectionView.
The collection view has custom cell.
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 12;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCollectionCell *yearCell = [collectionView dequeueReusableCellWithReuseIdentifier:myCellIdentifier forIndexPath:indexPath];
if (yearCell == nil)
yearCell = [[AgendaYearCollectionCell alloc] init];
yearCell.layer.shouldRasterize = YES;
yearCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
[yearCell setCurrentDate:newDate];
return yearCell;
}
I registered the nib of the custom cell in viewDidLoad:
UINib * nib = [UINib nibWithNibName:#"AgendaYearCollectionCell" bundle:[NSBundle mainBundle]];
[self.collectionView registerNib:nib forCellWithReuseIdentifier:myCellIdentifier];
MyCollectionViewCell is a custom (inherited) UICollectionViewCell and its setCurrentDate method does:
-(void)setCurrentDate:(NSDate *)date
{
if (calendar == nil)
calendar = [[myCalendarView alloc] initWithDate:currentMonth];
[self.contentView addSubview:calendar];
calendar = nil;
[self setNeedsDisplay];
}
The problem is that memory increase linearly as I add/remove new cell to the view.
I was supposing that dequeueReusableCellWithReuseIdentifier does what I need: reuse cells keeping memory usage low.
But this does not happen. For instance, my collection view is a calendar: a grid of 12 months. Therefore, I need always 12 and only 12 cells.
There is a way for a better management of the collection ?
I set my reuse cell identifier here
EDIT:
I think here is your problem, you add calendar each time in collection view delegate,so you just reused your MyCollectionCell but your calendar in MyCollectionCell is not reused.that's why you can see the memory print grow. So , you should make MyCalendarView more reusable so that you don't have to alloc it each time.
-(void)setCurrentDate:(NSDate *)date
{
if (calendar == nil){
calendar = [[myCalendarView alloc] initWithDate:currentMonth];
[self.contentView addSubview:calendar];
// calendar = nil;//here you dealloc calendar which make `if(calendar == nil)` run each time.
[self setNeedsDisplay];
}
}// each calendar in Collection Cell won't be create or refresh again.
Don't init CollectionViewCell like UITabelView cell in cellForItemAtIndexPath. Delete all of these code
// if (yearCell == nil)
// yearCell = [[AgendaYearCollectionCell alloc] init];
// yearCell.layer.shouldRasterize = YES;
// yearCell.layer.rasterizationScale = [UIScreen mainScreen].scale;
Because your cell is Nib cell, bring all these setting into awakeFromNib in MyCollectionCell.m like this
-(void)awakeFromNib{
[super awakeFromNib];
self.layer.shouldRasterize = YES;
}
registerNib is enough in the case of Nib cell, remember specify your MyCollectionCell in Nib cell class.
Use Instruments for analysing your app's memory footprint. In XCode > Product > Profile and select Leaks on Instruments. It is very very useful to trace high memory usage responsible calls.
I want to design a page like this image for Ipad:
I decide to implement it by UICollectionViewController, and I am going to use UICollectionViewController for the first time.
I want to know, UICollectionViewController let me have various layout for cells? and how can I do that?
Yes you can have different layouts for cells.
In your UICollectionView, you will need to register the various cell classes that you are going to use:
[self registerClass:[Class1 class] forCellWithReuseIdentifier:#"Class1"];
[self registerClass:[Class2 class] forCellWithReuseIdentifier:#"Class2"];
Then in your datasource delegate:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//Whatever logic you will use to distinguish between the two classes
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Class1" forIndexPath:indexPath];
//OR
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Class2" forIndexPath:indexPath];