Custom UICollectionViewCell increases memory allocation - ios

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.

Related

UICollectionView reloadData more than once - Facing a big major issue

I just want to create a UICollectionView as a linear list. That is if the user enters a 4 values in 4 repeats of the textfield, The text has to store in the MutableArray (Which determines the number of collection items).
The problem what I am facing is,
When the view appears, the collectionview starts loading. Initially, data is 0. So, after completion of inserting object in the array. The change should reflect in the Collectionview too. So, after reloading the collectionview, the first item is shown. But, from the next repeats, The data which is recently, entered in the array is inherited to all the cells. Please, help me what to do.
howmanypeople=[routescore.text intValue];
[self.collectionView reloadData]; // Reloading the view
This code is for item at indexPath :-
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = #"cvCell2";
static BOOL nibMyCellloaded = NO;
if(!nibMyCellloaded)
{
UINib *nib = [UINib nibWithNibName:#"CustomCollectionViewCell" bundle: nil];
[collectionView registerNib:nib forCellWithReuseIdentifier:identifier];
nibMyCellloaded = YES;
}
cell = (CustomCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell2" forIndexPath:indexPath];
cell.cellimage.image=[UIImage imageNamed:#"ic_lomobile_transit_scooter_small.png"];
cell.cellabel.text=route;
cell.backgroundColor=[UIColor lightGrayColor]
return cell;
}

How to load UICollectionView cells in the background

I have a collection view that displays instances of CatViewController views:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CatCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCatCellId forIndexPath:indexPath];
[cell updateCat:[self.dataSource catAtIndex:indexPath.row]]; //catAtIndex will return a Cat Core Data object
return cell;
}
CatCell looks like this:
- (void)prepareForReuse {
[super prepareForReuse];
[self.catVC.view removeFromSuperview];
self.catVC = nil;
}
- (void)updateCat:(Cat*)cat {
self.catVC = [[CatViewController alloc] initWithNibName:nil bundle:nil];
self.catVC.view.frame = self.bounds;
[self.contentView addSubview:self.catVC.view];
self.catVC.cat = cat;
}
self.catVC.cat is what causes the CatViewController to configure itself with all the view data associated with a Cat object. The problem is that when the UICollectionView scrolls, it pauses briefly as the new CatViewController is created and displayed. Obviously I want the collection view to be completely smooth, and have the view for each cell appear when it's ready, without blocking the main thread.
This is easy and well-documented to do with images, but I'm struggling to do the same with a view controller's view.
Reuse as much of your controller and view infrastructure as possible. There is a reason that table and collection views offer cell reuse - tear down and recreation is expensive.
Collection view memory management is the art of reuse. By not reusing the controller and it's view you are subverting the cell reuse (because you destroy and recreate 90% of the cell content each time.

Faster UICollectionView cells

There are a lot of answers out there for loading images into UITableViews or UICollectionViews. But what if my UICollectionView is displaying views from other view controllers?
Say in my UICollectionViewCell subclass I have this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.catViewController = [[CatViewController alloc] initWithNibName:nil bundle:nil];
self.catViewController.view.frame = self.bounds;
[self addSubview:self.catViewController.view];
}
return self;
}
And in my collection view Datasource:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:kSomeId forIndexPath:indexPath];
cell.catViewController.data = self.data[indexPath.row]; //see below
return cell;
}
The catViewController has a setter for the data property. When this property is set, the cat will load it's image, along with some other related images for that view. So how do I properly reuse the MyCell cells so that the collection view doesn't stutter each time it creates (or reuses) a cell? Each MyCell takes up the full width of the collection view, which scrolls horizontally, so every time a new cell scrolls into view, the collection view stalls for a moment.
For High performance CollectionView cells , Use following new stuffs in iOS10 with xcode8
Implement protocol "UICollectionViewDataSourcePrefetching" in you ViewController as
class ViewController: UIViewController , UICollectionViewDataSourcePrefetching {
Set following delegates to your collection view in storyboard (see the attached image)
or programmatically
In ViewController's viewDidLoad method
collectionView.delegate = self
collectionView.dataSource = self
collectionView.prefetchDataSource = self

UICollectionView with UITextView very slow

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.

Why am I getting a deallocated memory call when I call UICollectionViewCell dequeueReusableCellWithReuseIdentifier?

I have a UICollectionView that contains custom UICollectionViewCells (TestReceiptCell is the class name).
I was not having any problems getting the UICollectionView to appear and load the custom cells when the custom cells only contained a UILabel.
I then added a UITableView via IB into the TestReceiptCell NIB file. I set a referencing outlet in TestReceiptCell.h for the UITableView and synthesized in the .m file. I set the delegate and datasource for the UITableView to the ViewController containing the UICollectionView.
Now when running the app I get a EXC_ BAD_ ACCESS exception in this block on the third line:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"TestReceiptCell";
TestReceiptCell *cell = (TestReceiptCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; //exception thrown here
return cell;
}
I ran the Zombie Instrument test and found that the deallocated memory call originates here. This is my first time using that instrument so I am not exactly sure how to investigate from here.
For reference, here are some more relevant parts of the code:
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.myCollectionView registerNib:[UINib nibWithNibName:#"TestReceiptCell" bundle:nil] forCellWithReuseIdentifier:#"TestReceiptCell"];
// Setup flowlayout
myCollectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[myCollectionViewFlowLayout setItemSize:CGSizeMake(310, 410)];
[myCollectionViewFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.myCollectionView setCollectionViewLayout:myCollectionViewFlowLayout];
self.myCollectionView.pagingEnabled = YES;
}
I am implementing the UITableView datasource and delegate methods in the ViewController.m file as well but I am not sure if the problem lies here given the origination of the EXC_BAD_ACCESS exception:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"eventCell"];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"eventCell"];
}
return cell;
}
UPDATE:
I am able to get this to run if I change cellForItemAtIndexPath to:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"TestReceiptCell";
//TestReceiptCell *cell = (TestReceiptCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
TestReceiptCell *cell = [NSBundle.mainBundle loadNibNamed:#"TestReceiptCell" owner:self options:nil][0];
return cell;
}
However, I am not dequeuing cells and know this is not the correct way. There seems to be an issue somewhere in the initWithFrame method that gets called when dequeueReusableCellWithResueIdentifier creates a new cell. Here is that method currently:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:#"TestReceiptCell" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
}
return self;
}
EDIT:
If I do not select a delegate or a datasource for the tableview, the collectionview with tableviews will load. Something in attaching the delegate/datasource to File's Owner is causing the error.
When you register a UINib for cell reuse, dequeueReusableCellWithReuseIdentifier:forIndexPath: is what calls instantiateWithOwner:options: on the UINib that you registered. Whatever it passes for owner, is what becomes the File's Owner outlet in your nib.
It appears that you are expecting the File's Owner to be the UICollectionView, but I don't think that it is.
Even if it were, I don't think you should use the UICollectionView for the delegate of the UITableView contained within each collection cell. That would require your UICollectionView to keep track of the tableViews and contents within each cell.
I'd suggest setting the delegate of the contained tableView to the collection cell itself and have each cell manage its own tableview.
EDIT:
You can define a delegate protocol for your collection view cells to communicate the relevant table view events to the collection view. With this approach, you would set the delegate property you define for each collection cell in the collectionView:cellForItemAtIndexPath method of your collection view datasource.
When the user, for example, selects an item from the table, you can call the cell delegate to inform the collection view which item was selected.
This approach allows you to abstract the fact that your collection cell is using a table view to display the cell information. Later, if you decide you want to use, for example, an embedded UICollectionView to display those items, the delegate protocol can remain unchanged and you can isolate your changes to the collection cell.

Resources