I am working on accessing a custom check box which I have already utilized in my project as a custom button, and trying to access it in my table view cell.
Currently, it is prompting the button correctly, and works just fine, except for the case when I scroll my tableview, and then make any further changes on the button (either checkin, or checkout), the button image will overlay the older image and not update the draw.
I am just curious, is there any fix for this?
My code looks something like this:
my code for cellForRowAtIndexPath looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CustomTableCell";
CustomTableCell *cell = (CustomTableCell *)
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"CustomTableCell"
owner:self options:nil];
cell = tableCell;
self.tableCell = nil;
}
CheckBox *chkBox = [[CheckBox alloc] init];
chkBox.frame = CGRectMake(10.0, -10.0, 50.0, 70.0);
[cell.contentView addSubview:chkBox];
[chkBox release];
cell.modelLabel.text =
[[[[[self regData] ShoppingCart] objectForKey:#"Cart"]
valueForKey:#"Model"] objectAtIndex:indexPath.row];
// Configure the cell...
return cell;
}
I haven't implemented the didSelectRowAtIndexPath method yet as that method is suppose to take the viewer to newer screen with more information.
What you're doing is adding the Checkbox every time you display a cell, regardless of whether it's a new or reused cell.
You don't want to do this every time:
CheckBox *chkBox = [[CheckBox alloc] init];
chkBox.frame = CGRectMake(10.0, -10.0, 50.0, 70.0);
[cell.contentView addSubview:chkBox];
[chkBox release];
Only do this if it's a new cell... however by adding the checkbox as a subview and then releasing it, you're losing access to it anyway. To access it you'll need to iterate the subviews of the cell, find the one of type checkbox and then see if it's checked.
It would be better if your CustomTableCell had a Checkbox property that you set, so you could easily access it.
Related
I have a problem, setting a button to a UITableviewCell.
After viewDidLoad, the button is on the right place. But when I am scrolling down, the button is anyplace else.
Here is my code, I hope you can help me.
Thanks In Advance.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if (indexPath.section == 0 && indexPath.row == 0 && _isAddImageViewLoad == NO) {
// Add Image Button
UIButton *addImage = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* image = [UIImage imageNamed:#"AddImage#2x"];
addImage.frame = CGRectMake(110.0f, 10.0f, 110.0f, 110.0f);
[addImage setImage:image forState:UIControlStateNormal];
[cell.contentView addSubview:addImage];
_isAddImageViewLoad = YES;
} else {
NSDictionary *dictionary = [_items objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"data"];
NSString *cellValue = [array objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
}
return cell;
}
It is because you are reusing the cells, and the button is getting placed when it shouldn't an easy solution in your else section. Write addImage.hidden = YES; and in your if statement put addImage.hidden = NO;
Just a couple things. If you use "AddImage" it will use the "AddImage#2x" automatically if it's a retina display. I don't think that will solve your issue but it could be causing weirdness.
When a table view cell is scrolled off the view it is "recycled" in a sense. It appears like you are using a bool to exclude the original cell from being loaded again with a button. You may want to use a header to hold your button if you always want it at the "top". You may also want to verify that the button is being removed when the cell is reused. if its not it will show up in the next row that reuses that cell.
On a side note... Buttons don't usually work very well in table view cells because they handle touches in very different ways. It's quite a bit of modification to get them to feel natural but that's another matter.
Hope that helps!
The problem is because of cell reuse. You need to put some code in the else clause to delete the button if it exits. One way to do this, would be to give your button a tag, like:
addImage.tag = 10;
Then in your else clause:
}else{
if (cell viewWithTag:10) [[cell viewWithTag: 10] removeFromSuperview];
...
The problem is because of the dequeue for the cells. The first time the tableview creates the cells, all the cells run through the
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
code. But when the section 0 row 0 is moved off the screen, that cell is pushed into the cell reusable queue.
Now when your tableview needs to display section 0 row 0, it will get a cell from the reuse queue. you will not get the same cell as the first time. So now you might have 2 cells with the button.
What you should do is have different CellIdentifier for section 0 row 0 , and all other sections and rows. Also create the button when creating the cell. So after the first time the tableView creates the cell, you will not be creating the the button everything.
Look at this line of code:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
This line of code means the UITableViewCells are not created every time. They are re-used as you scroll up and down. Using the code you have above, the UIButton will be created in the correct spot, but then as the cells are re-used, it will create the button in random spots.
One quick way to solve the problem, change the above line of code to simply
UITableViewCell *cell;
I have an app consisting of a TabBar with a few TabBarControllers. One Controller contains a very simple table, which is supposed to display the contents of a NSMutableDictionary. When you hit the appropriate button, the Dictionary is updated in a separate Controller and the view switches to the UITableViewController, displaying the newly updated table.
I can see the Dictionary being updated. But the TableView never reflects the changes. In fact, it seems to display the changes only the 1st time I enter that screen.
I have tried [self table.reloadData] and while it gets called, the changes aren't reflected to the UITableView.
Does anyone have any suggestions? I am happy to post code, but am unsure what to post.
Update: the table is updated and refreshed properly only the 1st time it is displayed. Subsequent displays simply show the original contents.
Background:
The tableview gets filled from a dictionary: appDelegate.currentFave. The tableview should get refreshed each time the ViewController is invoked by the TabBarController.
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"in viewWillAppear");
[super viewWillAppear:animated];
[self loadFavesFile];
[self.tableView reloadData];
}
// load the Favorites file from disk
- (void) loadFavesFile
{
// get location of file
NSString *path = [self getFavesFilePath];
// The Favorites .plist data is different from the Affirmations in that it will never be stored in the bundle. Instead,
// if it exists, then use it. If not, no problem.
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
// read Faves file and store it for later use...
NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
appDelegate.sharedData.dictFaves = tempDict;
// grab the latest quote. Append it to the list of existing favorites
NSString *key = [NSString stringWithFormat:#"%d", appDelegate.sharedData.dictFaves.count + 1];
NSString *newFave = [NSString stringWithFormat:#"%#", appDelegate.currentFave];
[appDelegate.sharedData.dictFaves setObject:newFave forKey:key];
} else {
NSLog(#"Favorites file doesn't exist");
appDelegate.sharedData.dictFaves = nil;
}
}
// this gets invoked the very first call. Only once per running of the App.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse or create the cell
static NSString *cellID = #"cellId";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// allow longer lines to wrap
cell.textLabel.numberOfLines = 0; // Multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Chalkduster" size:(16)];
cell.textLabel.textColor = [UIColor yellowColor];
// NOTE: for reasons unknown, I cannot set either the cell- or table- background color. So it must be done using the Label.
// set the text for the cell
NSString *row = [NSString stringWithFormat:#"%d", indexPath.row + 1];
cell.textLabel.text = [appDelegate.sharedData.dictFaves objectForKey:row];
return cell;
}
I found the problem. I was not properly initializing and assignng the TableView in my view controller. See below
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
tableView.backgroundColor=[UIColor blackColor];
self.view = tableView;
}
Assuming the code you have put up is correct, you want to use [self.table reloadData]. You have the . in the wrong place.
I had this same problem yesterday, for me it turned out I had set the wrong file owner in interface builder and hadn't set up the data source and delegates for the table view properly.
Try going into interface builder and right-clicking on the file owner, this should show you if anything isn't connected up properly.
You should make sure that your Interface Builder connections are set up properly, but what this problem really sounds like is that you have your UITableViewCell setup code in cellForRowAtIndexPath: inside your if(cell == nil) statement. Which it shouldn't be. Let me explain. If you have a list of cells, and you want to set the titles to each cell to a string in an array called myArray, right now your (incorrect) code looks like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
}
return cell;
}
Can you see the problem with that logic? The cell will only get an updated title if no reusable cell can be found, which, in your case, sounds like the situation. Apple says that you should create a 'new' cell each time cellForRowAtIndexPath: is called, which means that you put all of your setup code outside of the if(cell == nil) check.
Continuing with this example, the proper code would look like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
}
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
This way, the cell gets assigned the proper string whether or not a reusable cell is found and so calling reloadData will have the desired effect.
UITableView not scrolling smoothly...(iPhone SDK) ..!!
I have implemented UITableView DataSource and Delegate methods in an individual separate classes.(one for delegate and one for datasource) in main program i write only:
//assume that all objects are allocated
ObjTableView.dataSource=ObjDataSource;
ObjTableView.delegate = ObjDelegate;
[self.view addSubView: ObjTableView];
when i run this code , UITable view appears but when i try to scroll it, it doesn't scroll smoothly.
I have also checked that UITableViewCell doesn't redraw once the cell is initialized.
can any one tell me why this happens ? How can i solve this problem ??
From comments:
ListDataSource *ObjListDataSource = [[ListDataSource alloc]initWithArray:[[sender object] valueForKey:#"List"]];
ListDelegate *ObjListDelegate = [[ListDelegate alloc]initWithArray:[[sender object] valueForKey:#"List"]];
tblList = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
tblList.dataSource = ObjListDataSource; tblList.delegate = ObjListDelegate;
[self.view addSubview:tblList]; [tblShopList release];
More from comments:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom cell code
}
return cell;
}
More Information:
I have used NSNotification which notifies to current class when parsing is complete, after receiving notification , current class method calls DataSource, Delegate methods (which is defined in a separate class file).
So UItableViewCell customization (which is in ListDataSource) and table view(in current class) both are in different classes.
A problem is
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
The id needs to be the same for all cells of the same class, otherwise you never reuse them. As you can see in most examples, it is indeed a constant in most (all?) cases.
Little explaination on the reuseIdentifier: every time a cell gets out of screen, you can reuse it instead of creating a new one. To reuse it, you need a cell in queue with the same identifier as the one you pass to dequeueReusableCellWithIdentifier. The way you did, the cells are never reused, because each id is unique (they may or may not be reused in case a row reappears on screen, depending on queue size, which is not configurable AFAIK). This is why personalization of the cell should happen OUTSIDE the "cell == nil" block. Long story short, you are using the reuseIdentifier not as intendend.
I think Michele is correct, but I would also add that it looks like you are doing your cell customization where the cell gets created. What you should be doing is something more like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = (UITableViewCell)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom REUSABLE cell code here, e.g. text color, etc.
}
NSString *cellText = [dataArray objectAtIndex:indexPath.row]; //assuming you have a simple array for your data
cell.textLabel.text = cellText;
return cell;
}
I would also add that I'm not sure why you are able to run the app with the code you have here, since UITableViewCell cell = ... is an invalid initializer. It should be UITableViewCell *cell = ....
It would be helpful to see how you are customizing your cell, since without that it's hard to see what's happening.
i have set up a tableview with custom cells. customCell is a class.
heres the code for a more accurate view:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *object = (NSManagedObject *)[entityArray objectAtIndex:indexPath.row];
NSString *cellIdentifier = [NSString stringWithFormat:#"asd%d", indexPath.row];
customCell *cell = [[tableView dequeueReusableCellWithIdentifier:cellIdentifier] autorelease];
//i tried setting a tag but dunno how to call it afterwards
[cell setTag:indexPath.row];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"customCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
if (cell.imagen != nil) {
[[cell imageView] setImage:[cell imagen]];
} else { /* setup image... */ }
}
-(void) webImageReady:(WebImage *)downloadedImage imageView:(UIImageView *)imageView cellTag:(NSInteger *)cTag
{
// This is the part where i want to access cell.imagen, this is actually wrong...
[[[imageView.superview viewWithTag:cTag] imagen] setImagen:downloadedImage.Image];
[imageView setImage:downloadedImage.Image];
}
Ok. now i want to access (reference) the cell.imagen property from a method outside cellForRowAtIndexPath, more precisely at a selector for a download finished (delegated)
Thanks in advance!
Do it inside cellForRowAtIndexPath if the image is downloaded, and on successful download of the image do [tableview setNeedsDisplay]
You shouldn't refer to the cell outside the cell creation method, you should consider the case the cell was rendered but while getting the image was scrolled out the dealloced or even reused for another cell, one way to solve it is to have image view array or something similar.
I think you should try using a third party lib that already doing it(among other things) called Three20. It have an object call TTImageView that gets a URL and loads it in the background, it solves all of the cases along with optimized caching
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.