Hi i am having a memory leak issue with the code bellow once my application is tested on the physical device. My problem occurs when scrolling a UiCollectioView dow but is also very slow on the load up of this view controller.
So what i am truing to do is use a NSFileManger to load images directly from the iPhones DCIM file located at this path /var/mobile/Media/DCIM/100APPLE/. After fetching these images i place them into an array and create a sort of gallery with them through the UICollectionView by populating each cell with the list of images in the array. For example cell 1 = image 1, cell 2 = image 2 and so forth. This works correctly but when scrolling crashes unexpectedly but forcefully so i am therefore assuming this is a memory leak problem. Especially when this problem does not occur on the simulator.
Here is my code:
- (void)viewDidAppear:(BOOL)animated
{
// Do any additional setup after loading the view from its nib.
path = #"/var/mobile/Media/DCIM/100APPLE/";
Images = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]mutableCopy];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
if ([[[Images objectAtIndex:indexPath.row]pathExtension] isEqualToString:#"JPG"])
{
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
static NSString *identifier = #"Cell";
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageWithData:[NSData dataWithContentsOfFile:[NSString stringWithFormat:#"%#%#", path, [Images objectAtIndex:indexPath.row]]]]];
UIView *v = [[UIView alloc]initWithFrame:CGRectMake(0, 80, cell.bounds.size.width, 20)];
v.backgroundColor = [[UIColor blackColor]colorWithAlphaComponent:0.4f ];
UILabel *title = [[UILabel alloc]initWithFrame:CGRectMake(0, 70, cell.bounds.size.width, 40)];
title.tag = 200;
title.text = [Images objectAtIndex:indexPath.row];
title.textColor = [UIColor whiteColor];
[cell.contentView addSubview:v];
[cell.contentView addSubview:title];
return cell;
}
else
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
static NSString *identifier = #"Cell";
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
return cell;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return Images.count;
}
Thanks in advance...
PS:
My app is being built as an app for jailbroken phones so please do not tell me that apple will not accept the way i am doing this.
First of all, you shouldn't assume you have a memory leak as it's pretty uncommon when using ARC; you should use instruments to test. You have a problem in that you're adding the view, v, and the label, title, to cells that already have them when you scroll, and cells are reused. This is most likely your problem. Personally, I think it's bad form to add subviews in cellForItemAtIndexPath, unless you're adding them to some cells and not others based on the indexPath. You should create a custom cell, and add the subviews in its init method (or in IB). Also, you only need to register the class once, so it shouldn't be in cellForItemAtIndexPath; a better place to put it would be viewDidLoad.
Related
My application getting crashed on iPhone4 for frequent use of 5 to 10 mins.
I having some images in application itself , have internal SQLite file, image names are stored in sqlite table and used from the name.
Also fetch some images from online DB via url (NSURL). Online DB images are loaded in UIImageView using SDWebImage framework. But still am getting "Received memory warning" and app getting terminated
I have checked with instruments its shows the image getting more data and lead to crash
getting array of images name from DB and loading image to UIImageView present in UICollectionViewCell
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell1 = [self->collectionView dequeueReusableCellWithReuseIdentifier:#"cell1" forIndexPath:indexPath];
for (UIImageView *lbl in cell1.contentView.subviews)
{
if ([lbl isKindOfClass:[UIImageView class]])
{
[lbl removeFromSuperview];
}
}
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, cell1.frame.size.width, cell1.frame.size.width)];
imgView.backgroundColor = CLEAR_COLOR;
imgView.contentMode = UIViewContentModeScaleAspectFit;
UIImage *imv = [UIImage imageNamed:(NSString *)mArray_FeatIcon[indexPath.item]];
imgView.image = imv;
[cell1.contentView addSubview:imgView];
imv = nil;
return cell1;
}
image loading from weburl in UICollectionView
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell1 = [cvGallery dequeueReusableCellWithReuseIdentifier:#"cell1" forIndexPath:indexPath];
for (UIImageView *lbl in cell1.contentView.subviews)
{
if ([lbl isKindOfClass:[UIImageView class]])
{
[lbl removeFromSuperview];
}
}
UIImageView * imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, cell1.frame.size.width, cell1.frame.size.height)];
imgView.backgroundColor = [UIColor clearColor];
NSString *urlString = [[NSString stringWithFormat:#"%#",mArrThumbUrl[indexPath.item]] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[imgView setImageWithURL:[NSURL URLWithString:urlString] placeholderImage:[UIImage imageNamed:#"logo.png"]];
[cell1.contentView addSubview:imgView];
return cell1;
}
Not able to add screen shot of instruments, because of low reputation. Thanks in advance.
May be this is due to leakage of memory management in iPhone. Please refer this tutorial link and check for NSZombies in Instruments .
Follow this link-> I think this is very useful.
http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
I've got a UICollectionViewController which displays blocks of telephone numbers (see image). When the view loads they all appear fine however when i either begin scrolling, changing rotation, or execute a search function which alters the (mutable) array in which the data is sourced, i see these malformed labels. I did think it might be the iOS simulator however from looking at it, it appears to be an issue with the positioning of UICollectionViewCells.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor grayColor];
cell.layer.cornerRadius = 5;
[cell setClipsToBounds: YES];
CGRect cellBound = CGRectMake(25, 12.5, 150, 12.5); // x, y, w, h
UILabel *title = [[UILabel alloc] initWithFrame:cellBound];
NSString *number = [[searchNumbers objectAtIndex:indexPath.row] valueForKey:#"number"];
number = [number stringByReplacingOccurrencesOfString:#"+44" withString: #"0"];
title.text = number;
[cell addSubview:title];
return cell;
}
It should be noted that i am using UICollectionViewFlowLayout
As #Woodstock mentioned, this is due to "over-adding" UILabel objects to your cell.
Rather than his solution, which still adds the UILabel to the cell in -collectionView:cellForRowAtIndexPath:, the better MVC solution is this:
// A UICollectionViewCell subclass
// Make sure to pick the correct "init" function for your use case
- (instancetype)init... {
self = [super init...];
if (self != nil) {
[self setupCell];
}
return self;
}
- (void)setupCell {
self.backgroundColor = [UIColor grayColor];
self.clipsToBounds = YES;
self.layer.cornerRadius = 5;
CGRect cellBound = CGRectMake(25, 12.5, 150, 12.5); // x, y, w, h
// Assumes you've set up a UILabel property
self.titleLabel = [[UILabel alloc] initWithFrame:cellBound];
[cell addSubview:self.titleLabel];
}
- (void)configureWithNumber:(NSString *)number {
number = [number stringByReplacingOccurrencesOfString:#"+44" withString: #"0"];
self.titleLabel.text = number;
}
// In your UICollectionViewDataSource/Delegate implementation
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSString *number = [[searchNumbers objectAtIndex:indexPath.row] valueForKey:#"number"];
[cell configureWithNumber:number];
return cell;
}
Basically, you want to set up and add views only when setting up the cell initially. After that, you should pass in a data value/object and configure the cell. If you have cells that need different controls (2 labels vs. 1, etc.), then make multiple subclasses. This way, you encapsulate your classes for cleaner code and better reuse.
I believe this is happening because you are adding more and more UILabel subviews to your cell (over and over again as cellForItemAtIndexPath is called). You need to add a check and only add a label subview if the cell doesn't already have one. The dequeued cells already have the label subview if they're being reused, if this label already exists you simply need to set it's text from your datasource.
Pseudocode:
for subview in subviews {
if subview.isKindOfClass(UILabel) {
// assign the new text label.
}
else
{
// create and add the UILabel subView.
}
}
This is an easy mistake to make as dequeueReusableCellWithReuseIdentifier can either give you a previously used cell OR as you've seen give you a fresh one initially. Which is why the app works correctly when you start, but gets messy as you scroll.
Goal:
Set up two collectionViews with different styles. One grid style and other single file style. These collectionViews will be toggleable giving users shopping within the ability to view the items for sale in which ever style (grid/single file) they prefer.
I have an existing collectionView with a custom cell set up in interface builder and it's working fine. I've tried to find info on how to add a second one via interface builder but have had no luck in finding any.
What I've done:
I've created the second collectionView programatically in my viewDidLoad method. I have an instance variable named _collectionView2.
- (void)viewDidLoad
{
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
_collectionView2 = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
[_collectionView2 setDataSource:self];
[_collectionView2 setDelegate:self];
[_collectionView2 registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[_collectionView2 setBackgroundColor:[UIColor redColor]];
[self.view addSubview:_collectionView2];
[_collectionView2 setHidden:YES];
I've modified delegate and datasource methods to make them aware about the new collectionView:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (collectionView == _collectionView) {
NSArray *people = [_thisController objects];
return [people count];
} else {
return 20;
}
}
.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
if (collectionView == _collectionView) {
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[[cell activityIndicator] startAnimating];
PFFile *userImageFile = [object valueForKey:#"image"];
[[cell imageView] setFile: userImageFile];
[[cell imageView] loadInBackground];
[[cell activityIndicator] stopAnimating];
[[cell title] setText:[object valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%# GBP", [object valueForKey:#"price"]]];
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor greenColor];
return cell;
}
//_addToFavouritesButton = [cell addFavouriteButton];
[_addToFavouritesButton addTarget:_thisController action:#selector(addToFavouritesButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (collectionView == _collectionView2) {
return CGSizeMake(50, 50);
} else {
return CGSizeMake(140, 272);
}
}
Then I have a method that responds to the UISegmentControl being switched from grid style display to single file display:
- (void)displayTypeSegmentSelected
{
_selectedDisplayTypeIndex = [_displayTypeControl selectedSegmentIndex];
if (_selectedDisplayTypeIndex == 0) {
NSLog(#"Single file item view selected");
[_collectionView setHidden:YES];
[_collectionView2 setHidden:NO];
} else {
NSLog(#"Grid style view selected");
[_collectionView setHidden:NO];
[_collectionView2 setHidden:YES];
}
}
CollectionView2 is hidden initially then unhidden when the segment control is used. It looks like it's working because when I toggle a red background shows and I did set a red background when I created the collectionView but green cells aren't showing at all.
I can't seem to see where I've gone wrong. I'd like to get this working so I can replace the cell with my custom cell. The only difference between grid display and single file display is that single file display will be an enlarge cell that takes up the width of the view. All properties etc will be the exact same meaning I can use my current custom cell.
Why aren't my cells showing? I've followed a clear tutorial on creating a collectionView programmatically but still no cells are showing.
Update:
I put a log message in the if statement that checks what collectionView is present and it is only ever trigger for my _collectionView and not _collectionView2.
Help is appreciated.
Regards
Not sure this will fix your issue, but when I have 2 collection views in one controller, I use their tag's to determine which one I am dealing with in a given function, like cellForItemAtIndexPath.
I also have always used different cell identifiers for the two cells and usually different subclasses of uicollectionviewcell.
Where do you create the first collection view and set its delegate?
Reloading the table data in the method my UISegmentedControl triggers got the cells to show up.
I am using UICollectionView to show all the images available in my library using assets, its displaying all the images but when I scroll up and down many times , I receive a memory issue in the device and after some time it gets crashed saying CRASHED DUE TO MEMORY PRESSURE
code used are as follows
creates here the collectionview and settings its delegate
UICollectionViewFlowLayout *layout=[[UICollectionViewFlowLayout alloc] init];
allPhotosCollectionView=[[UICollectionView alloc]initWithFrame:CGRectMake(5, 5, 310, 481)collectionViewLayout:layout];
[allPhotosCollectionView setDelegate:self];
[allPhotosCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[allPhotosCollectionView setDataSource:self];
[self.view addSubview:allPhotosCollectionView];
delegate methods
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
{
return [allImageArray count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
return 1;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(95, 100);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
NSString *identifier = #"Cell";
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
// allimageArray has the Assest URL fo the images
NSURL *aURL=[[NSURL alloc]initWithString:[NSString stringWithFormat:#"%#",[allImageArray objectAtIndex:indexPath.row]]];
[assestLibrary assetForURL:aURL resultBlock:^(ALAsset *asset)
{
UIImage *copyOfOriginalImager = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullScreenImage] scale:0.5 orientation:UIImageOrientationUp];
UIImageView*imageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 90, 90)];
NSData *imageData;
imageData=UIImageJPEGRepresentation(copyOfOriginalImager, 0.4);
[imageView setImage:[UIImage imageWithData:imageData]];
[cell.contentView addSubview:imageView];
}
failureBlock:^(NSError *error)
{
// error handling
NSLog(#"failure-----");
}];
return cell;
}
I am facing many problems in iOS 7 , app crash due to memory pressure , please explain that too
its due to assest library. Once u fetched the image using assest libary just store it in some variable and re-use that image in
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
while scrolling the collection view it will fetch the image again and again using asset libary this will cause huge memory leak.
Edit:
UIImage *img =[imageDictionary objectForKey:imageName];
if(!img)
{
NSURL *asseturl = [NSURL URLWithString:imageName];
img = [McAssetReader readImage:asseturl];
[imageDictionary setObject:img forKey:imageName];
}
where imageDictionary is the Global Dictionay for holding the image.
imageName is the url for particular image.
There are two issues with your code:
You're keep adding imageViews to cell without even checking if one already exist, hence memory usage is growing rapidly;
You're not caching results of your image transformation and keep reading/transforming on every cell dequeue, hence you're keep pressure on disk IO and memory;
Cache and reuse technics will most likely fix your problems.
I can see issue here. Surely It will lead to memory error.
UIImageView*imageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 90, 90)];
.
.
.
[cell.contentView addSubview:imageView];
See this line. You are creating imageView everytime and holding that image into imageview.
So you can do this instead of first line.
UIImageView *imageView = [cell.contentView viewWithTag:ImageViewTag];
if (!imageView)
{
imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 90, 90)];
[cell.contentView addSubview:imageView];
}
I'm working on a project similar to a video album. In that I'm using UICollectionView to display the thumb images of those videos. The worst part is that I should not use storyboard or xib files. I have tried to do this programatically. I'm currently working on this code:
- (void)viewDidLoad
{
i = 0;
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setItemSize:CGSizeMake(self.view.frame.size.width/2.5,self.view.frame.size.width/2.5)];
collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
[collectionView setDataSource:self];
[collectionView setDelegate:self];
collectionView.backgroundColor = [UIColor whiteColor];
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"MyCell"];
[self.view addSubview:collectionView];
[super viewDidLoad];
}
I have given 1 for return in numberOfSectionsInCollectionView and [myArray count] for return in numberOfItemsInSection.
-(UICollectionViewCell *) collectionView:(UICollectionView *)cV cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [cV dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blackColor];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.origin.x , cell.frame.origin.y, cell.frame.size.width, (cell.frame.size.height - cell.frame.size.height/3))];
cell.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.origin.x , cell.frame.origin.y, cell.frame.size.width, (cell.frame.size.height - cell.frame.size.height/3))];
imageView.image = [UIImage imageNamed:[storeData objectAtIndex:i]];
[cell addSubview:imageView];
i++;
return cell;
}
I have rechecked the images in myArray. When the view loads, the collection view shows only the first image. Other 4 cells are empty. What is wrong with my code?
For those who is experiencing this problem, frame is the key. I've encountered this and changed:
cell.imageView.frame = cell.frame;
into
cell.imageView.frame = cell.bounds;
The op is using:
cell.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(cell.frame.origin.x , cell.frame.origin.y, cell.frame.size.width, (cell.frame.size.height - cell.frame.size.height/3))];
That's why this happened.
You shouldn't be using i as a counter. The whole point of the delegate method sending you an indexPath is that it tells you what information to get from your array of source data. So, remove i and use the indexPath.row instead.
You also don't need 2 image views. But you should probably keep your special subview and not use the cells built in image view.
You do not need a counter. As indicated by Wain, use indexPath.row.
Importantly, you should not create new subviews in cellForItemAtIndexPath, but rather use this method to fill them appropriately with content. You could put the image views into your storyboard prototype cells and identify them with tags. Each cell returned from dequeueReusableCellWithReuseIdentifier will already contain the image view.
you should never create ui elements in cellForRowAtIndexPath:. Subclass a collection view cell like so:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSLog(#"INIT WITH FRAME FOR CELL");
//we create the UIImageView here
imageView = [[UIImageView alloc] init];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.frame = CGRectMake(cell.frame.origin.x , cell.frame.origin.y, cell.frame.size.width, (cell.frame.size.height - cell.frame.size.height/3));
[self.contentView addSubview:imageView]; //the only place we want to do this addSubview: is here!
}
return self;
}
Then add that subclassed cell as a property and alter this code:]
[collectionView registerClass:[customCellClass class] forCellWithReuseIdentifier:#"MyCell"];
The perform these changes:
-(customCellClass *) collectionView:(UICollectionView *)cV cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = (customCellClass *)[cV dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor blackColor];
imageView.image = [UIImage imageNamed:[storeData objectAtIndex:indexPath.row]];
return cell;
}
A final adjustment would be to move the the [super viewDidLoad] to this:
- (void)viewDidLoad
{
[super viewDidLoad];
//insert the rest of the code here rather than before viewDidLoad
}