Image at 0 index overtake - iphone

I've got this nasty problem. My class, which is subclassing UITableViewController, has a method which is invoking
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
The method which is invoking what is above is this one:
- (void) insertInfo:(UIImage *)image
{
self.hotelImage = nil;
self.hotelImage = image;
numRows = numRows + 1;
[self.tableView beginUpdates];
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
This is how I am creating a cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
self.datacell = nil;
self.datacell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"SimpleTableIdentifier"] autorelease];
self.myImageView = nil;
self.myImageView = [[UIImageView alloc] initWithImage:self.hotelImage];
[self.myImageView setFrame:CGRectMake(5, 5, 80, 75)];
[self.datacell.contentView addSubview:self.myImageView];
[self.myImageView release];
self.tableView.userInteractionEnabled = YES;
self.tableView.separatorColor = [UIColor clearColor];
return self.datacell;
}
So, my problem is that when I scroll the table, all images in the table get replaced by a single image at index 0. My guess is that it has something to do with the fact that when cell is created, every image in the section is considered at index 0, but different image is shown when table is created. But when the table is scrolled by user, different images get overtaken by an image the is in the index 0 of the first cell.
This is exactly what happens, the image on the first cell is shown on all cells when the table begins to scroll.
I just don't know how to make every cell retain its unique image when the table is scrolled. My guess is it has something to do with placing image indexPath.section??? But I am not sure. Could somebody please help me with this problem?
Thank you,
Victor.

Effectively you're editing the contents of your table with insertInfo, but not editing its actual source, so as soon as it needs to redraw those cells, they get reverted to its original source. Try using something along the lines of:
self.myImageView=[[UIImageView alloc] initWithImage:[myImageArray objectAtIndex:indexPath.row]];

The fact is that the tableView doesn't keep your cells around. As soon as you scroll them out of view they are gone, released, deallocated as far as you know, and the table view asks the data soucre for new replacement cells. Your cellForRowAtIndexPath is always returning the same cell, independent of what the table view asks for.
You should be storing the images sent to insertInfo into an array, then in cellForRowAtIndexPath, always return the image from the array by index, looked up by indexPath.row and indexPath.section. Every time you scroll, that cellForRowAtIndexPath is called to get all the new cells that weren't on screen, this is why the cells always "change to" the most recent image sent to insertInfo.
Try adding a new file to your project, subclass of UITableViewController, and check out the default code there. You'll find dequeueReusableCellWithIdentifier is also a useful one.
The key concept here is that cellForRowAtIndexPath: is a question, and your code needs to check the values of indexPath.section and indexPath.row, and return a value that belongs to that row/section.

Related

how to fill one row with an image in a UITableView

I'm creating a UITableView, containing information about a company. It has multiple sections, and I filled most cells with just a textlabel and a detailtextlabel through 2-dimensional array's. I want 1 cell (the second cell of the first section to be exact) to be filled with an image.
The first thing that popped up in my mind, was creating another 2-dimensional array, and fill it with empty objects, except for the one image. however, that doesn't make sense and i'm sure there are more professional and logical ways to do this.
So the question is: How do I fill one tableViewCell with an image, in a tableview with multiple sections. any help would be highly appreciated, thank you in advance!
You need not to create a multidimension array.Just add a check in cellForRowAtIndexPath method of UITableView datasource
if(indexpath.section == 0)
and set the background image of cell content view or you can also create a new UIImageView and add that in cell as a subview.
If you expect a few such exceptions, you can create a dictionary of exceptions, where the keys are NSIndexPath objects, the values images. Then in tableView:cellForRowAtIndexPath: check to see if the dictionary contains the index path. If so, use the image, else go to your array for data.
Your problem is not uncommon, the only thing to understand now is the delegate methods give you enough information about the section and the row displayed. Now in those methods you can use your datasource and check for the required conditions and construct/return the cell.
It will be something on these lines:
(UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath*)indexPath {
// When you get to the section where you want customisation create and return the cell accordingly
if(indexpath.section == 0) {
// create Special Cell
} else {
// Create Normal Cell
}
return cell;
}
Dont forget to reuse the created cells using dequecellwithreuseindentifier method. I am sure you can figure that out.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.section==0 && indexPath.row==1) {
UIView *v=[[UIView alloc] initWithFrame:cell.frame];
[v setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"image1.png"]]];
cell.backgroundView=v;
}else{
UIView *v=[[UIView alloc] initWithFrame:cell.frame];
[v setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"image2.png"]]];
cell.backgroundView=v;//OR SET cell.backgroundView=nil;
}
}
You can use -
(UITableViewCell *)tableView:(UITableView *)atable cellForRowAtIndexPath:(NSIndexPath*)indexPath {
cell.imageView.image = [UIImage imageNamed:#"image.png"];
return cell;
}
I hope, it will help you.

iOS: UITableView clarifications

How can I eliminate views created upon selecting a cell in UITableView when the cell is no longer selected or other cell is on selection.
Suppose I have the code below for didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
CGSize cellSize = [tableView cellForRowAtIndexPath:indexPath].frame.size;
UIView *selectionView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, (int)cellSize.width, (int)(cellSize.height + 100))];
selectionView.layer.borderWidth = 2;
[[tableView cellForRowAtIndexPath:indexPath]addSubview:selectionView];
}
Now, I want to remove that created selectionView when I focus on another cell and create it again to the cell which I am focusing.. The problem is that when I select a cell for the first time, it works perfectly but when I select another cell, the selectionView created from the previous cell still does not disappear and it duplicates the view already. How am I suppose to solve this? Need suggestion.. :( thanks..
You need to add tag for the selectionView as follows
selectionView.tag = 100;
Also , you need to have the reference of the last selected indexPath by declaring a NSIndexPath class member and retaining it.
So while selecting a new cell, get the cell with last selected indexpath and remove the view from the cell as follows
UITableViewCell *lastCell = [tableView cellForRowAtIndexPath:lastSelIndexPath];
UIView *view = [lastCell viewWithTag:100];
[view removeFromSuperview];
You really need to look into subclassing UITableViewCell if you want a custom selection UI. Then override [UITableViewCell setSelected:animated:] (or [UITableViewCell setHighlighted:animated:]) to perform your customisations.

Create grid table view in iphone

I want to make a table view in which i want to show some items or images ( 4 items in a row)For eg: If i have 7 images i want to show 4 in first row and 3 in next row and if i have 13 images then it should display like 4,4,4,1 in rows 1,2,3,4 respectively. I have achievd this but my problem is those images keep changing their position (shift from 1 to 3 row and vice versa) when i scroll the table view. Please tell me some way so that my images automatically adjust themselves 4 in a row and does not repeat if i scroll the table view.
My code is:
(array has 7 image-datas stored in it)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 93;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Avi"];
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Avi"] autorelease];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSData *imageData;
for(int i=0;i<4;i++){
imageData=[array objectAtIndex:indexpath.row];
UIButton *Button = [UIButton buttonWithType:UIButtonTypeCustom];
Button.frame = CGRectMake(5+(i*77), 5, 77,85);
[Button setBackgroundImage:[UIImage imageWithData:imageData] forState:UIControlStateNormal];
[cell.contentView addSubview:Button];
}
return cell;
So the code snippet you've posted isn't particularly helpful, but this almost certainly is a case of not recycling your table view cells properly.
Table views 'recycle' their cells for efficiency and performance - when a cell scrolls out of view it is marked for recycling: when you next request a new cell you will be given a recycled cell if one is available (otherwise a new cell will be created).
If all your cells use the same layout, this isn't a problem. However, if you adjust the layout of your cells (as you do) this can cause problems, because you might have set up a cell for 2-picture layout and have it be recycled for a 4-picture cell.
You have two options here: either take care to reset your cells as required (when you request a new cell reset all the view parameters you've created, which in your case would be by removing all the superviews), or create different cells with different identifiers for each of your layouts.
By the way, just looking at your current code it seems like you may want to consider some custom cells. Currently you add new images to the cell's contents view, which means since you never remove them every time your cell gets recycled more and more and more images get added! It's quite inefficient. Better to create a cell that has four image views ready to go, and you just assign the image property as required.

Button added to custom section header view disappears when row is deleted

Just came across some very strange behavior in my app. I've recreated the problem in a simplest-case scenario:
NSMutableArray *data;
- (void)viewDidLoad {
[super viewDidLoad];
data = [[NSMutableArray arrayWithObjects:#"1", #"2", #"3", nil] retain];
}
- (UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section {
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 32.0)];
header.backgroundColor = [UIColor lightGrayColor];
[header addSubview:self.button];
return header;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[data removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
cell.textLabel.text = [data objectAtIndex:indexPath.row];
return cell;
}
- (void)dealloc {
[super dealloc];
}
Every time I delete a row; the button in my header disappears! This happens no matter what type of rowAnimation I use. If I scroll the table up so that the header scrolls off; the button returns when the header returns. The button is created in the xib file.
I can work around it in one of 2 ways:
Reloading the tableView data after the delete; with a delay so that the deletion animation completes first.
Creating the button in viewForHeaderInSection instead of in the interfaceBuilder.
I'd really like to understand what's going on here. Where is the button going? I've confirmed that viewForHeaderInSection is called when I delete a row.
Edit I tried changing it so that the button is created in viewForHeader, instead of in the xib, but it's causing other strange issues... when I create or delete the button, I am setting certain properties such as the title and enabled depending on how many items there are in the table. When I delete the last row in the table, I don't see the update in text and enabled status until I scroll the button off the screen and back on again.
Because you only have one instance of your button, if the table view decides to create a new header view then the button will be removed from its current parent and moved to the new one. Even if you only have one section in your table, the table view may be doing some strange things internally and recreating header views off-screen so you can't rely on just one being in existence at any one time.
You should create the button in viewForHeaderInSection: and work around your other problems. Rather than only updating the button properties in viewForHeaderInSection you should handle any delete events so that deleting a row will also update the button.
Where is your implementation of the delegate method tableView:heightForHeaderInSection: ? That is necessary for tableView:viewForHeaderInSection: to work correctly. Check the docs.
Reference for UITableView delegate
I've confirmed that
viewForHeaderInSection is called when
I delete a row.
Have you confirmed that viewForHeaderInSection is called for the particular header with the added button?
Then, try adding
[header bringSubviewToFront:self.button];
after adding the button.
Well I at least managed to get around my issue... I made an iVar and property for the view that I create in viewForheaderAtSection, and then I only create a new view if I don't have one already. Otherwise I just return the header I already had; something like this:
- (UIView *)tableView:(UITableView *)aTableView viewForHeaderInSection:(NSInteger)section {
if (!self.myHeader){
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.frame.size.width, 32.0)];
header.backgroundColor = [UIColor lightGrayColor];
[header addSubview:self.button];
self.myHeader = header;
[header release];
}
return self.myHeader;
}
This works, but it would still be great to understand what exactly is going on. As far as I can tell, viewForHeaderInSection is being called by the system, but then the instance of the view that I return in that method is not actually being used / shown; at least not until I do something that causes the view to redraw...

Problem updating UITableViewCells when rotating UITableView

I have a UILabel in a custom UITableViewCell that gets resized when the device is rotated. The text in this label needs to be recalculated after the rotation because I am cutting it down to size and appending some text at the end.
E.g. the datamodel has: "This is a run-on sentence that needs to stop."
In portrait mode it becomes "This is a run-on sent... more"
In landscape mode it becomes "This is a run-on sentence that... more"
From (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
I am able to access the visible UITableViewCells and update the descriptions.
The problem seems to be that there are UITableViewCells that are cached but I can't get to. When I scroll the UITableView after a rotation, one or two cells that are below the visible area after the rotation don't have the correct text in the label. So they haven't been rendered via (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath - but they weren't returned by [tableView visibleCells] (or via looping through all views returned via [tableView subViews]).
I've tried to access the "extra" cells via this method:
for (int index=max + 1; index < max + 3 && index < [cellTypes count]; index++) {
NSIndexPath *updatedPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:updatedPath];
if (cell == nil) { continue; }
[self updateCellForRotate:cell forRow:index];
}
(where max is the biggest row returned from visibleCells) but cell is always nil.
Is there anyway to flush the cache of UITableViewCells so that they don't get re-used? Or to access them so I can update them?
Thanks!
Two things.
First. In your didRotateFromInterfaceOrientation method you can simply reload the visible rows like so:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation) fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
NSLog(#"didRotateFromInterfaceOrientation:%d",fromInterfaceOrientation);
[tableView reloadRowsAtIndexPaths:[tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationNone];
}
Then I would recommend you add either the interfaceOrientation number or simply the table width to the dequeue cell name that way the tableView knows that cells in one rotation are different from those in another. Like so:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath withType:(NSString *)s_type
{
UITableViewCell *cell = nil;
// add width of table to the name so that rotations will change the cell dequeue names
s_cell = [s_cell stringByAppendingString:[NSString stringWithFormat:#"%#%d",#"Width",(int)tv.bounds.size.width]];
NSLog(#"%#",s_cell);
cell = [tv dequeueReusableCellWithIdentifier:s_cell];
if( cell == nil ) {
cell = [[[UITableViewCell alloc];
initWithFrame:CGRectZero reuseIdentifier:s_cell] autorelease];
}
}
Firstly, to reload all of your table cells use [self.tableView reloadData]
Secondly, add the line of code that is responsible for the shrinking inside the (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method.
Example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Some identifier and recycling stuff
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
//Make labels smaller
}
else {
//Make them bigger
}
}
Or you can just call your updateCellForRotate:forRow: method when making them. But I'm not sure how that function works, so I can't be too specific.
When you create the cell in cellForRowAtIndexPath:, add it to an array. Then, loop through the array, updating the text as necessary.
Hope this helps,
jrtc27
EDIT:
You say they are custom cells - could you not update your text in your UITableViewCell subclass?
So, I was having (what I think was) a very similar problem recently, and none of the posted answers helped me, I'm sorry to say.
My issue was that I deliberately resized and repositioned the UITableView upon rotation, and I did that programatically. The table cells in portrait took up the width of the view, and in Landscape were made somewhat higher but less wide. I then repositioned the elements of the cell depending on the orientation we'd come to.
Upon application start, the first viewing of the table was fine. Then I rotated and found that I appeared to have two instances of some elements, and these appeared to be where the cells had been visible in the first table. Rotating back then corrupted the initial orientation table with elements from the previous table.
I tried all of the applicable answers above, until I looked closer at the cellForRowAtIndexPath code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
I understand cell re-use is a great idea and all, but I really didn't need to retain (as in preserve) any cells and wanted them all bright, spangly and new after each rotation.
EDIT: In my own app I'll have maybe 20-30 rows maximum, as I personally don't like hugely long tables. If there were going to be lots of rows returned for a particular query I'd have some filters available to the user to help them sort out which rows they wanted. If you're going to have loads of rows displayed, then dequeuing them may cause you a performance impact that you don't want.
All I did was comment out the if and the following bracket, and my table cells renewed exactly as I wanted them to:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
//}
Apologies for the waffle, and the late answer to an old question.
Ben.
Waffles and cream, or syrup.
You can use this simple line on the shouldAutorotateToInterfaceOrientation method :
self.view.autoresizesSubviews = YES;
For me it works always successfully