iam developing one application.In that i place the imageview in every tableview cell like
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
itemimageview=[[UIImageView alloc]initWithFrame:CGRectMake(5, 8, 75, 70)];
itemimageview.image=[UIImage imageNamed:#"thumb_mdpi.png"];
itemimageview.userInteractionEnabled = YES;
[cell.contentView addSubview:itemimageview];
return cell;
}
And when u click any row we get the image from our iPhone camera.For that i write the below code in didSelectROw method.
UIImagePickerController *imagePicker = [[[UIImagePickerController alloc] init]
autorelease];
imagePicker.sourceType=UIImagePickerControllerSourceTypeCamera;
//imagePicker.allowsImageEditing = NO;
imagePicker.delegate = self;
[self presentModalViewController:imagePicker animated:YES];
After i use the UIIMagePickerController delegate method like
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage* image = [info objectForKey:UIImagePickerControllerOriginalImage];
NSLog(#"image selected");
itemimageview.image =image;
[self dismissModalViewControllerAnimated:YES];
}
But my problem is after selecting the image,that image will be appear at last cell only.If u select first row and take the image then that image will be appear at last row.So please tell me what are the changes in my code for getting the image for every row.
You can achieve this by following these steps:
1.First when the user clicks a cell, store the row number (indexPath.row) in an integer variable.
2.Once you have acquired the image from the camera,store it in a UIImage.
3.Now you know the row to be updated and you have ur image.Call reloadData method of the UITableView at the end of the didFinishPickingMediaWithInfo delegate method.
4.The reloadData method will subsequently call the cellForRowAtIndexPath method. In that have a condition like this:
if(indexPath.row==clickedRow){
//Create the imageView here and then assign the image obtained from the camera to it.
imageView.image=cameraImage;
[cell.contentView addSubView:imageView];
}
where clickedRow is the integer variable which holds the row number clicked by the user in didSelectRowAtIndexPath method.
Hope this helps.
The problem is that you are setting itemimageview to the image view inside the most recent cell the tableview grabbed from the delegate method each time a cell is drawn so itemimageview will contain the last image that the tableview drew.
In order to fix it you will have to make sure to update the imageview of the cell that was actually clicked. I think the easiest way to do this would be to use use Apple's default cell imageview with cell.image. Or you could make a subclass of UITableViewCell that keeps track of and draws the UIImageView. Then you change the image in that cell that is selected.
Either way you will need to keep track of which cell was selected in that class. One way could be to put UITableViewCell selectedCell; in the .h. then selectedCell = [self cellForRowAtIndexPath:indexPath]; in your didSelect method followed by selectedCell.image = image in your image delegate.
Sorry if this is hard to follow, it's a short answer to a moderately complex problem. There are many ways to tackle this.
Related
I have a UICollectionView where I display a grid of images downloaded from the Internet. I am using SDWebImage to load the images. The problem I am facing is that, when I scroll through the UICollectionView, sometimes the cells repeat themselves, displaying the same image. But when the cell is scroll out of view and then brought back, it has the right image set.
-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier = #"Gallery_Cell";
GalleryCell *cell;
if (cell==nil) {
cell= (GalleryCell *)[self.flowCollection dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
ImageItem *dets = [self.imageList objectAtIndex:indexPath.row];
NSURL *mainImageURL = [NSURL URLWithString:dets.smallImageURL];
cell.image.contentMode = UIViewContentModeScaleAspectFill;
cell.image.clipsToBounds = YES;
[cell.image setImageWithURL:mainImageURL placeholderImage:nil];
}
return cell;
}
Has this happened to anyone else? Would highly appreciate any pointers.
On GalleryCell.m you need to add prepareForReuse method and there nullify the _image variable (assuming that you have a image #property in the cell):
- (void)prepareForReuse {
[super prepareForReuse];
[self setHighlighted:NO];
_image = nil;
}
The following is stated in the "UICollectionView Class Reference":"
If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithFrame: method. For nib-based cells, this method loads the cell object from the provided nib file. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead."
Do you have a prepareForReuse method for your GalleryCell cell class?
you cell class should be a subclass of UICollectionReusableView Class. And check it.
I ended up using “didEndDisplayingCell” method of UICollectionView and ending the current Image download and setting the image to nil. This worked perfectly and there was no more “shuffling” or repetition of images! :)
use this method this vl definitely work 100%.
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
{
GalleryCell *cell1 = (GalleryCell*)[_resumeCollectionView cellForItemAtIndexPath:indexPath];
cell1= nil;
}
-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier = #"Gallery_Cell";
GalleryCell *cell;
if (cell==nil) {
cell= (GalleryCell *)[self.flowCollection dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
ImageItem *dets = [self.imageList objectAtIndex:indexPath.row];
NSURL *mainImageURL = [NSURL URLWithString:dets.smallImageURL];
[cell.image setImage:[UIImage new]];
cell.image.contentMode = UIViewContentModeScaleAspectFill;
cell.image.clipsToBounds = YES;
[cell.image setImageWithURL:mainImageURL placeholderImage:nil];
}
return cell;
}
Since the cells are being reused, you must set their contents unconditionally in cellForIndexPath:. Your code as it is sets the images eventually, but it leaves the chance for the images to be temporarily left as old values -- values set before they were reused.
A quick solution is (probably) to provide that setImageWithURL call with a placeholder image. I don't know the SDImage library, but it's a good guess that it immediately sets an imageView's image to the placeholder image if one is provided.
If you don't have or don't want a placeholder image, you can implement prepareForReuse in your UICollectionViewCell subclass and clear the imageView there.
I'm creating a Settings View for my app, and in that view is a UITableView. I'm creating custom cells to meet my needs, but I'm having issues - only the last cell is getting [layoutSubviews]. Am I doing something wrong?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//int type = (indexPath.row == 0?1:0);
//if(indexPath.row == 6) type = 2;
NSLog(#"row %i created", indexPath.row);
TableCell *cell = [[TableCell alloc] initWithType:indexPath.row];
cell.textLabel.text = #"Test cell";
return cell;
}
And in my custom cell:
#implementation TableCell
UIImageView *shadowView;
int row;
- (id) initWithType:(int)type {
row = type;
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
self.backgroundColor = [UIColor clearColor];
self.backgroundView = [[UIView alloc] init];
UIImage *shadowImage = [UIImage imageNamed:#"CellShadow"];
shadowImage = [shadowImage resizableImageWithCapInsets:UIEdgeInsetsMake(14, 14, 14, 14)];
shadowView = [[UIImageView alloc] initWithImage:shadowImage];
[self.contentView addSubview:shadowView];
//[self.contentView sendSubviewToBack:shadowView];
NSLog(#"agreed, row %i created", row);
[self layoutSubviews];
return self;
}
- (void) layoutSubviews {
NSLog(#"row: %i", row);
[super layoutSubviews];
shadowView.frame = CGRectMake(
0, 0,
self.contentView.frame.size.width,
self.contentView.frame.size.height
);
}
#end
Continuously, only the last cell #6, is reported when I rotate, or when layoutSubviews should be called. Any suggestions?
Do not call layoutSubviews directly. Use [self setNeedsLayout] or [self layoutIfNeeded]. But do not call these at all in the cell's init method.
Also, do not call [[TableCell alloc] initWithType:indexPath.row]; directly, either. Instead, use...
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
Once you've built that cell, you can tell it it's row, but be aware that the cells get recycled as the table scrolls, so you must update that value on every call to cellForRowAtIndexPath.
The cells ought to get layout again (without you making any calls direct or indirect) when the table view is resized.
See the tableview doc here.
You should never call layoutSubviews directly, it will be called automatically by iOS once the cell is ready to display. You should also deque the cell as #danh is recommending. If you're not very comfortable with all this, then I'd really recommend you have a look at the free Sensible TableView framework, which automates creating these kind of settings views (I create mine in a couple of lines, really).
The issue was of my own poor code. Using cell.backgroundView helped a lot here.
Never Call layoutSubviews by yourself. It will be called when ever frames of subview in cell are changed. Even if just change the text of labels in your custom cell wont call layoutSubviews. Ue the deque of cells for reusing for better performance. As it wont allocate cell every time. And in you code looks like has lot of memory issues since cell allocated wont be released and new cell is created.
I've created a custom UITableViewCell but am having trouble updating the contents of the cell. When I have multiple cells in the table, the table is not drawing the correct images in the cell. The images in each cell should be unique, however I am seeing different cells with the same image. The table seems to be placing the cells at random.
I've checked my data source with NSLog and the names are correct. I can correct the issue when I don't use - (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier, but instead create a new cell each time in - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath.
Any suggestion on what I may be doing wrong? Please have a look at my code below.
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ScoreCell *cell = (ScoreCell *)[_tableView dequeueReusableCellWithIdentifier:#"CellID"];
if (cell == nil)
{
cell = [[[ScoreCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellID"] autorelease];
}
BoxScore *boxScore = [_gameDayData objectAtIndex:indexPath.row];
[cell setScoreImage:[UIImage imageNamed:boxScore.name]];
return cell;
}
ScoreCell.h
#interface ScoreCell : UITableViewCell
{
UIImage *scoreImage;
}
#property(nonatomic, retain)UIImage *scoreImage;
#end
ScoreCell.m
#implementation ScoreCell
#synthesize scoreImage;
- (void)dealloc
{
[scoreImage release], scoreImage = nil;
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[scoreImage drawAtPoint:CGPointMake(5,5)];
}
#end
In addition to the other comments, be sure to implement the -prepareForReuse method in your ScoreCell class. It gets called when the cell is to be reused, at which point you should clear the image. Be sure to call [super prepareForReuse]; in your implementation. This will prevent the cell from being reused with the wrong image.
There are two non-related problems with your image handling.
Scrolling off and on the screen will cause a cell to load the image twice (or a hundred times, depending on the user).
You want a
- (UIImage *)boxScoreImageForIndex:(NSInteger)index
method to (lazy) load, hold on to, and provide the image for the cell.
You also don't want to use imageNamed:, in your case it will cause twice as much memory usage than needed. Use imageWithContentsOfFile: instead.
You are not clearing the previous image. When a cell is dequeued it is not dealloced.
So sometimes the image that was drawn on the cell the times before it show through in front of the new image.
In drawRect you need to clear everything.
Either do:
CGContextClearRect( context , [self bounds] );
Or set clearsContextBeforeDrawing on the cell when it is created.
Let's say I have a property in my view controller, defined as follows:
#property (nonatomic, retain) UIImageView *checkmarkOffAccessoryView;
I #synthesize this in the implementation, release it in -dealloc and initialize it in -viewDidLoad as follows:
self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
So far so good.
When I use it in my table view delegate as an accessory view for multiple cells, two things happen:
Only one cell's accessory view shows the image
The application UI freezes.
The app doesn't crash, as near as I can tell, the UI simply becomes unresponsive. This is both in the simulator and on the device.
Here is how I use the initialized property with my cell:
- (UITableViewCell *) tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// initialize or dequeue cell...
if (condition)
cell.accessoryView = self.checkmarkOffAccessoryView;
else
cell.accessoryView = nil;
}
With the aforementioned code, only one cell shows the accessory view and the UI freezes.
If I initialize the UIImageView instance directly in the delegate method I get all condition-satisfying cells showing the accessory view and I do not experience the UI freeze:
- (UITableViewCell *) tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// initialize or dequeue cell...
if (condition)
cell.accessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
else
cell.accessoryView = nil;
}
My goal is to initialize as few objects as possible and reuse one UIImageView. I'm curious why the first chunk of code is problematic and what I could do to fix this.
It seems like the cell's accessoryView property should just increment the retain count of self.checkmarkOffAccessoryView but it appears I am missing some detail.
What have I overlooked? Thanks for your advice.
EDIT
I think that:
self.checkmarkOffAccessoryView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
is the same as:
UIImageView *uncheckedView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmarkOff.png"]];
self.checkmarkOffAccessoryView = uncheckedView;
[uncheckedView release];
Either way, I experience the same freeze symptom.
You cannot add the same view multiple times. The UI handler will go bonkers. To make sure of this, I tried doing what you said above and I got the same issue. The UI freezes up, the image only appears for one of the cells.
The best thing you can do is to store your image as a UIImage allocated, and to have a helper function which returns a new UIImageView per cell.
Using your current method (without a stored UIImage) you might do:
-(UIImageView *) makeCheckmarkOffAccessoryView
{
return [[[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"checkmarkOff.png"]] autorelease];
}
And then do
cell.accessoryView = [self makeCheckmarkOffAccessoryView];
As you may be aware, UIImages on the other hand may be used any number of times. a UIImageView doesn't take up a lot of space, so you can easily have a bunch of those without worrying.
To expand on the one place only deal, imagine that you add a UIView to two places at the same time.
What will [ob removeFromSuperview] do for this object? Will it remove the view from both places? From one of them only? Which value will be returned when you request [ob superview]? Clearly the UI is not made to handle what you're asking for.
Try it without the autorelease in the initializer. I suspect you're over-releasing.
By the way, your console probably is showing a BAD_ACCESS error when it freezes. If you turn on NSZombieEnabled, my guess is you'll see it's making a call to a deallocated UIImage.
maybe this will help
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ShoppingListCell";
HSShoppingListCell *cell = (HSShoppingListCell *)[aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"ShoppingListCell"
owner:self
options:nil];
cell = shoppingListCell;
}
ShoppingListItem *theItem = nil;
theItem = [self.fetchedResultsController objectAtIndexPath:indexPath];
UIImage *selected = [UIImage imageNamed:#"listBullet_checked.png"];
UIImage *notSelected = [UIImage imageNamed:#"listBullet.png"];
cell.imageView.image = ([theItem.checkedOff boolValue] ? selected : notSelected);
cell.shoppingListLabel.text = theItem.productName;
[cell.shoppingListLabel setFont:[UIFont fontWithName:#"Marker Felt" size:26.0]];
return cell;
}
- (void)toggleCellImage:(NSIndexPath *)indexPath
{
ShoppingListItem *item = [self.fetchedResultsController objectAtIndexPath:indexPath];
item.checkedOff = ([item.checkedOff boolValue] ? [NSNumber numberWithBool:NO] : [NSNumber numberWithBool:YES]);
[HSCoreDataUtilities saveContext:item.managedObjectContext];
[self.tableView reloadData];
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self toggleCellImage:indexPath];
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Reducing your case to the bare essentials (I was going to suggest to put two 'thin' UIView objects around the UIImageView...), I found that it is most probably impossible.
Create 2 empty UIView objects in IB, hook them up to bareView1 and bareView2. Then
UIImageView *imageView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"test.png"]];
[bareView1 addSubview:imageView]; // it shows either here ...
[bareView2 addSubview:imageView]; // ... or here
You can never get the image on sceen more than once like this. As a rule of thumb, I think the first object in line which does not inherit from UIView can be used multiple times, i.e. the UIImage. Like Kalle stated, a UIView can only have one parent in the view hierarchy.
Postponing the second addSubview only makes the UIImageView jump from bareView1 to bareView2.
The freeze happens maybe because the event handling gets mixed up: the accessory can be interactive, how would you know which one was tapped if they are one and the same object? So the code assumes objects are unique, and you manage to violate that assumption.
We couldn't find a way to animate UIViews inside a UITableCell as an action from a callback.
Suppose in a scenario where you click on a button on the UITableViewCell and it fires off an asynchronous action to download a picture. Suppose further that when the picture is downloaded we want a UIView in the cell to animate the picture to give user a visual feedback that something new is about to be presented.
We couldn't find a way to track down the UIVIew to invoke beginAnimation on because the original cell that the user clicked on might now be used for another row due to the nature of cells being reused when you scroll up and down in the table. In other words we can't keep a pointer to that UITableViewCell. We need to find another way to target the cell and animate it if that row is visible and don't animate if the row is scrolled out of range.
Keep the cell object different from the object being animated so the cell holds a UIView. When the animation callback occurs check to make sure that the UIView still exists and, if it does, animate the changes.
When the cell object gets bumped off the screen and recycled, release the UIView that would have been animated and create a new one. When the animation callback occurs it will have nothing to do because the UIView no longer exists.
A modification of the above is to keep some sort of object in the UIView that your callback can check to see if the animation is still appropriate. This could be some sort of unique identifier for the picture being downloaded. If the identifier changes, no animation is needed. If it matches, do the animation.
EDIT:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier] autorelease];
} else {
UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
[oldViewToAnimate removeFromSuperview];
}
UIView *viewToAnimate = [[UIView alloc] initWithFrame:CGRectZero]; //replace with appropriate frame
viewToAnimate.tag = 1;
[cell.contentView addSubview:viewToAnimate];
return cell;
}
When you spawn your download process you pass in [cell.contentView viewWithTag:1]. When the download is done, it will update the appropriate view. If the table cell was reused the view will no longer have a superview and will not update the wrong cell.
There are things you can do to make this more efficient but this is the basic idea. If you have a custom UITableViewCell than this will probably look a bit different.
EDIT 2:
To reuse the viewToAnimate objects to make sure that they get updated if their parent cells were recycled, do something like the following:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier] autorelease];
} else {
UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
[oldViewToAnimate removeFromSuperview];
}
UIView *viewToAnimate = [self viewToAnimateForIndexPath:indexPath];
viewToAnimate.tag = 1;
[cell.contentView addSubview:viewToAnimate];
return cell;
}
viewToAnimateForIndexPath will need to:
Check to see if a viewToAnimate has been created for this indexPath
Create a viewToAnimate if there isn't one
Save a reference to the view that can be looked up by indexPath
Return the viewToAnimate so the table cell can use it
I don't know enough about your data structure to do this for you. Once the download process completes it can call this same method to get the view and animate it.