EDIT: I have figured out the answer on my own but here it is for anyone else who needs it:
UIImageViews cannot be shared so a different instantiation of each UIImageView is required for each visible cell. Now you know.
I have a custom table that has 2 types of cells. One cell is just set to toggle between a normal accessory of type checkmark. Another cell is set to have a custom image as the accessory type. When selected that accessory image changes to its opposite type, showing an "Invited" or "Invite" message.
I've narrowed down the code at fault to the following, found within my tableView:cellForRowAtIndexPath delegate method.
if(indexPath.section == 0){
cell = [tableView dequeueReusableCellWithIdentifier:self.directCellID];
cellValue = [self.contactsUsingApp objectAtIndex:indexPath.row];
cell.imageView.image = [self getContactImage:indexPath.row];
//vvvvvvvvvvvvvvvvv This is the section at fault vvvvvvvvvvvvvvvvv
if([self.selectedContactsUsingApp containsObject:indexPath])
cell.accessoryView = self.invitedStatus;
else
cell.accessoryView = self.notInvitedStatus;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
If I comment out that section I no longer have runaway memory usage (the Simulator showed me that there was some sort of constant allocation going on, it got up passed 1.29Gb after starting from 40Mb) but, obviously, the images no longer show.
If it matters the UIImageViews are initialized as follows:
UIImage *invite = [self imageWithImage:[UIImage imageNamed: #"invite_btn.png"] scaledToSize:CGSizeMake(40, 20)];
UIImage *invited = [self imageWithImage:[UIImage imageNamed: #"invited_btn.png"] scaledToSize:CGSizeMake(40, 20)];
self.notInvitedStatus = [[UIImageView alloc]initWithImage:invite];
self.invitedStatus = [[UIImageView alloc]initWithImage:invited];
(imageWithImage:scale is a function that returns a resized image to the appropriate scale accounting for retina found here: The simplest way to resize an UIImage?)
The same freezing happens when I select one of the cells because my tableView:didSelectRowAtIndexPath method works by the same toggling logic as the initialization method.
Help?
I have figured out the answer on my own but here it is for anyone else who needs it: UIImageViews cannot be shared so a different instantiation of each UIImageView is required for each visible cell. Now you know.
Related
I was using awakeFromNib to set an image with this code
self.cellImage01 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"mushroom_risotto.jpg"]];
self.cellImage01.frame = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height);
self.cellImage01.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.cellImage01.contentMode = UIViewContentModeScaleAspectFit;
Then I needed indexPath to get the image info from it's data source so I moved the code from awakeFromNib to cellForItemAtIndexPath. Now it displays differently
it doesn't have the same scaleAspectFit
going to landscape changes it to a small slice of the pic.
The small slice image only happens when the cell goes out of view, then back into view.
I'm using cell reuse and that seems to be where things are changing, but I can't access the code for that to see what's going on.
Does anyone know the call stack it goes thru for cell reuse?
Why would the aspect work in awakeFromNib and not in cellForItemAtIndexPath?
The common element here is that cellForItemAtIndexPath calls this first:
ArticleCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ArticleCollectionViewCell" forIndexPath:indexPath];
Edit: I should have noted that it displays properly when called in awakeFromNib.
This question already has an answer here:
UITableCell AccessoryView: Setting accessoryView equal to UIImageView infinite loop
(1 answer)
Closed 6 years ago.
I have a custom UITableView that has 2 types of cells. One cell is just set to toggle between a normal accessory of type checkmark. Another cell is set to have a custom image as the accessory type. When selected that accessory image changes to its opposite type, showing an "Selected" or "Unselected" message.
if (self.selectedIndex == indexPath.row) {
cell.accessoryView = self.selectedImageView;
}else
cell.accessoryView = self.unSelectedImageView;
If it matters the UIImageViews are initialized as follows:
-(UIImageView *)selectedImageView
{
if (!_selectedImageView) {
_selectedImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Check"]];
}
return _selectedImageView;
}
-(UIImageView *)unSelectedImageView
{
if (!_unSelectedImageView) {
_unSelectedImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"noCheck"]];
}
return _unSelectedImageView;
}
i saw someone answer this question:UITableCell AccessoryView: Setting accessoryView equal to UIImageView infinite loop
he said :I have figured out the answer on my own but here it is for anyone else who needs it: UIImageViews cannot be shared so a different instantiation of each UIImageView is required for each visible cell. Now you know.
but i don't know why?
A UIView can only be a subview of one parent view, so once you have added your UIImageView to one cell you can't add it to any others.
UIImage doesn't have this limitation, so you can modify your code to have selectedImage and unSelectedImage methods along the lines of what you have now or even modify your current methods so that they return a new UIImageView each time.
I'm having an issue,
I have a simple UICollectionView with a static 200 cells that load images from Flickr.
my CellForItemAtIndexPath looks like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
if(![[cell.subviews objectAtIndex:0] isKindOfClass:[PFImageView class]])
{
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
PFImageView *imageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
[cell addSubview:imageView];
}
return cell;
}
PFImageView is a subclass of UIImageView that loads a Flickr photo URL on a background thread and then updates it's own image on the main thread - this works fine.
The logic is really simple - I create a cell if there isn't one dequeueable.
If the cell (which I'm expecting to be dequeued and already have a PFImageView) doesn't have a PFImageView, I alloc and init an imageView for the cell and add it as a subview of the cell.
Thus I expect if the cell has been dequeued it should already have a PFImageView as a subview and as we should not get into the if statement to create a new imageView and kick off a new photo download request
Instead what I see is that the cells at the top and bottom of the UICollectionView that 'go off screen' momentarily - when they come back on screen they are not being reused and seemingly a new cell is created and the picture refreshed.
1) How can I achieve a static image once the cell has been created (i.e. not refreshing when the cell goes slightly off screen.
2) Why are the cells not being reused?
Many thanks for your time.
John
UICollectionView will reuse cells for maximum efficiency. It does not guarantee any particular reuse or population strategies. Anecdotally, it seems to place and remove cells based on integer power of two regions — e.g. on a non-retina iPad it might divide your scroll area up into regions of 1024x1024 and then populate and depopulate each of those regions as they transition into and out of the visible area. However you should not predicate any expectations on its exact behaviour.
In addition, your use of collection view cells is incorrect. See the documentation. A cell explicitly has at least two subviews — backgroundView and contentView. So if you add a subview it will be at index 2 at the absolute least and, in reality, the index will be undefined. In any case you should add subviews to contentView, not to the cell itself.
The most normal way of doing what you're doing would be to create a custom UICollectionView subclass that inherently has a PFImageView within it.
I see several potential issues:
You are looking specifically at index 0 of the cell for the child class that you are adding. The UICollectionViewCell may have other views as children, so you can't just assume that the only (or first) child is the one you added.
I don't see that you are calling registerClass:forCellWithReuseIdentifier: or registerNib:forCellWithReuseIdentifier:, one of which is required for proper use of dequeue (https://developer.apple.com/library/ios/documentation/uikit/reference/UICollectionViewCell_class/Reference/Reference.html).
You are only setting the URL of the PFImageView in the case that you have to construct the PFImageView. The idea with dequeuing reusable views is that you will only construct a small subset of the views needed, and the UITableView will recycle them as they move offscreen. You need to reset the value for the indexPath that is being requested, even when you don't construct the new content.
If your case is as simple as you describe, you can probably get away with adding your PFImageView to the contentView property of your dequeued UICollectionView.
In your controller:
// solve problem 2
[self.collectionView registerClass:[UICollectionViewCell class] forReuseIdentifer:#"FlickrCell"];
In collectionView:cellForItemAtIndexPath
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
// solve problem 1 by looking in the contentView for your subview (and looping instead of assuming at 0)
PFImageView *pfImageView = nil;
for (UIView *subview in cell.contentView.subviews)
{
if ([subview isKindOfClass:[PFImageView class]])
{
pfImageView = (PFImageView *)subview;
break;
}
}
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
if (pfImageView == nil)
{
// No PFImageView, create one
// note the use of contentView!
pfImageView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.contentView.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell.contentView];
[cell.contentView addSubview:pfImageView];
}
else
{
// Already have recycled view.
// need to reset the url for the pfImageView. (Problem 3)
// not sure what PFImageView looks like so this is an e.g. I'd probably remove the
// URL loading from the ctr above and instead have a function that loads the
// image. Then, you could do this outside of the if, regardless of whether you had
// to alloc the child view or not.
[pfImageView loadImageWithUrl:staticPhotoURL];
// if you really only have 200 static images, you might consider caching all of them
}
return cell;
For less simple cases (e.g. where I want to visually lay out the cell, or where I have multiple children in the content), I typically customize my UICollectionViewCell's using Interface Builder.
Create a subclass of UICollectionViewCell in the project (In your case, call it PFImageCell).
Add an IBOutlet property to that subclass for the view I want to change in initialization (In your case, a UIImageView).
#property (nonatomic, assign) IBOutlet UIImageView *image;
In Interface Builder, create a prototype cell for the UITableView.
In the properties sheet for that prototype cell, identify the UICollectionViewCell subclass as the class.
Give the prototype cell an identifier (the reuse identifier) in the property sheet.
Add the view child in interface builder to the prototype cell (here, a UIImageView).
Use IB to map the IBOutlet property to the added UIImageView
Then, on dequeue in cellForRowAtIndexPath, cast the dequeued result to the subclass (PFImageCell) and set the value of the IBOutlet property instance. Here, you'd load the proper image for your UIImageView.
I am not sure if the cell is being re-used or not. It may be being reused but the subview may not be there. My suggestion would be to create a PFImageViewCollectionViewCell Class (sub class of UICollectionViewCell) and register it as the CollectionView Cell and try. That's how I do and would do if I need a subview inside a cell.
Try adding a tag on this particular UIImageView
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static int photoViewTag = 54353532;
UICollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"FlickrCell" forIndexPath:indexPath];
cell.backgroundColor = [self generateRandomUIColor];
PFImageView *photoView = [cell.contentView viewWithTag:photoViewTag];
// Create a view
//
if (!photoView) {
photoView = [[PFImageView alloc] initWithFrame:CGRectMake(0, 0, cell.frame.size.height, cell.frame.size.width) andImageURL:staticPhotoURL andOwningCell:cell];
imageView.tag = photoViewTag;
[cell.contentView addSubview:imageView];
}
// Update the current view
//
else {
NSURL *staticPhotoURL = [self.context photoSourceURLFromDictionary:[self.photos objectAtIndex:indexPath.row] size:OFFlickrSmallSize];
photoView.imageURL = staticPhotoURL;
}
return cell;
}
I would really recommend to create your own UICollectionViewCell subclass though.
EDIT: Also, note that I used the contentView property instead of adding it directly to the cell.
Problem:
I use the following code inside the method cellForRowAtIndexPath to set the size of the image for the cell, yet at runtime the image gets blown up to the maximum height and width that the table row will allow.
UIImage *_image = [imageDictionary objectForKey:#"image"]; // Get image data
[_image drawInRect:CGRectMake(0,0,50,50)]; // set size
[cell.imageView setImage: _image]; // assign image to cell
cell.imageView.frame = CGRectMake(cell.imageView.frame.origin.x,cell.imageView.frame.origin.y,50,50);
return cell;
Question: Is there a more robust method of controlling the size of the image in a UITableViewCell? The approach I'm taking comes from several other posts but for some reason its being ignored in my code.
Side-note: I'm using Xcode 5 and developing on an iOS 7 platform.
Use UITableViewCell contentView .
The content view of a UITableViewCell object is the default superview for content displayed by the cell. If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
Example:
UIImage *_image = [imageDictionary objectForKey:#"image"]; // Get image data
[_image drawInRect:CGRectMake(0,0,50,50)]; // set size
UIImageView *imageView = [[UIImageView alloc] initWithImage: _image];
[imageView setFrame:yourFrame];
[cell.contentView addSubview:imageView];
This doesn't answer the question but your underlying problem is your approach. You should be customizing your cells by subclassing UITableViewCell. To add to that it's a lot easier to manipulate cell contents as views than to play around with the default picture and text label they give you. To carify, the contents of the cell sit on a view known as contentView accessible as cell.contentView. You can add text labels, buttons, and images as subviews to any location with any size you want the same way you would do with any view added as a subview.
Here is a video of the problem: http://youtu.be/Jid0PO2HgcU
I have a problem when trying to set the image for the [cell imageView] in a UITableView from the asset library.
The image is loaded but it doesn't appear in the cell until I touch (select) that cell. Here is my code that sets the imageView of the cell:
//Start loading the image using the image path
NSString *path = [occasion imagePath];
if (path != nil && [path hasPrefix:#"ass"])
{
NSLog(#"photo loaded from assets library");
//testing ALAssestLibrary
ALAssetsLibrary* assetsLibrary = [[ALAssetsLibrary alloc] init];
ALAssetsLibraryAssetForURLResultBlock resultsBlock = ^(ALAsset *asset)
{
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
[[cell imageView] setImage:[image imageByScalingAndCroppingForSize:CGSizeMake(36.0, 42.0)]];
[image release];
};
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error){
NSLog(#"FAILED! due to error in domain %# with error code %d", error.domain, error.code);
// This sample will abort since a shipping product MUST do something besides logging a
// message. A real app needs to inform the user appropriately.
abort();
};
//Convert path to an NSUrl
NSURL *url = [NSURL URLWithString:path];
// Get the asset for the asset URL to create a screen image.
[assetsLibrary assetForURL:url resultBlock:resultsBlock failureBlock:failureBlock];
// Release the assets library now that we are done with it.
[assetsLibrary release];
}
The above code is part of the:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
Any ideas?
I think in this case you're using a iOS defined UITableViewCell (and not a custom one), that is one of those cells that you get from "initWithStyle:reuseIdentifier:". Now these cells are internally built in such way that if you don't assign some content immediately the corresponding subview will not be added in the cell's contentView hierarchy. This kind of behavior is typical of complex custom views, where the final cell layout can be determined only when all its subviews have their content fully specified. (E.g. you have two labels, let's say label A and label B, label A is multirow and you want to add label-B immediately below label-A, then you can place label-B correctly only once you have the label-A content and you calculate its height.). So I suppose the built-in iOS table cells are done in the same way and the subviews hierarchy is built at the "layoutSubviews" stage.
In your case you create the cell but defer the time the image is provided. But at this point the layoutSubviews has been called yet and without any image the corresponding imageView is not even allocated and added in the contentView hierarchy!
So, according to me, the solution for you is just to assign immediately a "placeholder" for your asset (the placeholder can be a transparent image of course) which will guarantee you of the internal UIImageView creation. Be careful with the image size, it should be close to the expected image size, for the same reason explained above. As soon as your finish block is called the image view should be already there and the image will appear.
The reason why when you tap on the cell the image appears, is due to the fact that this operation calls the layoutSubviews again. In this case the whole code is probably re-executed and as you already called "setImage:" before then the internal "image" property is set and the contentView hierarchy will be rebuilt with the new image.
Another possible solution to your problem is of course to use a "custom" UITableViewCell (e.g. loaded from a Nib). In such case all your subviews will be loaded and you will simply access to the UIImageView using his tag (read apple docs to know more about this useful technique to create table view cells from a Nib file).
you need to layout cell after image set
just like
[cell setNeedsLayout];
I scratched my head for so long and finally figured it out.
My mistake was that I was setting image in cell.imageView when I should be setting my actual outlet cell.eventImageView. It was messing with the generic imageview provided in UITableViewCell. Hope it helps somebody.
This is my way how i solved these problem, i know is not the best but that work :)
UIImage *thumbnailImage = ...
dispatch_async(dispatch_get_main_queue(), ^{
[cell.imageView setImage:thumbnailImage];
UITableViewCellSelectionStyle selectionStyle = cell.selectionStyle;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell setSelected:YES];
[cell setSelected:NO];
cell.selectionStyle = selectionStyle;
});
In my case I was setting up a prototype cell through a Storyboard, but was accidentally using the dequeueCellWithIdentifier:forIndexPath method instead of dequeueCellWithIdentifier:.
The first method is only to be used when appropriating cells to a tableview through the programmatic UITableView registerClass:forReuseIdentifier: or registerNib:forReuseIdentifier methods.
I was having a similar problem. I had registered the default UITableViewCell class instead of my custom class.
I had this:
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: visibilityCellIdentifier)
but I should have had this:
tableView.registerClass(VisibilityCell.self, forCellReuseIdentifier: visibilityCellIdentifier)
The cell creation all looked to be working as I stepped through the debugger, but nothing showed up until I selected each row.
I get same issue when i use this code:
cell = [tableView dequeueReusableCellWithIdentifier:kCellSection1 forIndexPath:indexPath];
if (indexPath.row == 0) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
cell.textLabel.text = #"Vote Up for me if it help for you!";
}
But I fix it by replace:
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
To:
cell = [cell initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellSection1];
Sometime it happens when you are using custom UITableViewCell with labels, images etc but also somewhere you are setting default UILabel of cell. for example
customCell.textLabel.text = #"some text"
To solve this problem, avoid using default label, instead add your own UILabel in your custom cell.