Custom editingAccessoryView behaving unpredictable - iphone

Okay, first off - how I want my cells to look in my UItableView in editing mode (with some nicer buttons):
However - this is how it looks right now:
My problem is that my custom EditingAccessoryView only appears on the cell that I first created, and that those circle-thingies (what are those called?) appears. Which doesn't do much good.
Now, my code looks like this (which seems like the common way of doing this, seen at this question for example: How to add Custom EditingAccessoryView for UITableView?)
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.editingAccessoryView = AccessoryView;
AccessoryView.backgroundColor = [UIColor clearColor];
}
I have read that you are supposed to be able to call your custom editingAccessoryView by swiping, even if - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath returns "NO". This however I have not been able to achieve.
So, to sum it upp; I want my editingAccessoryView to be displayed at all cells - and, if possible, remove the red circle. Or, alternatively, call my editingAccessoryView when I swipe - what method gets called when the user does this?
Any help would be much appreciated.

I suggest you subclass table cell and override following method
- (void)layoutSubviews
{
if([self isEditing])
{
// your custom stuff
[self.contentView addSubview:myButton];
}
}

You need to make a new accessoryView for every cell.

Instead of setting all this in the cellForRowAtIndexPath.... do this.
In cellForRowAtIndexPath
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
And set your accessory view in
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
NSLog(#"Am I Editing");
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
selectedCell.editingAccessoryView = AccessoryView;
AccessoryView.backgroundColor = [UIColor clearColor]
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class,
// insert it into the array, and add a new row to the table view
}
}

To remove red circles you can return UITableViewCellEditingStyleNone in
- (UITableViewCellEditingStyle)tableView:(UITableView*)tableView editingStyleForRowAtIndexPath:(NSIndexPath*)indexPath;
But in this case the empty space for that circle is still there. I'm almost sure, it can be removed too.
My problem is that my custom EditingAccessoryView only appears on the cell that I first created
Try to instantiate new AccessoryView for each cell instead of using the same one.

Related

Custom UITableViewCell backgrounds, when to reload, etc

I've set up a custom grouped tableview style using the following code in cellForRowAtIndexPath. Each cell is given a new background view and it's corners are rounded according to its position in the section. I use this style throughout my app and in various views I use animations to add and delete rows. (Using [tableview insertRowsAtIndexPaths:], etc).
If the last row of a section is inserted or removed I need to be able to reload the cells background view corners. How can I do this without calling [tableview reloadData] or even reloadCellsAtIndexPaths? Both of those functions mess up the animations of inserting and deleting cells. Is there somewhere I can put this background corner definition that would get called at the appropriate time? How does the default grouped tableview do this?
Sorry if this is unclear. Ask for clarification and I will edit. Thanks! :)
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:[self styleForCellAtIndexPath:indexPath]
reuseIdentifier:cellIdentifier] autorelease];
// Theme the cell
TableCellBackgroundView *backgroundView = [[TableCellBackgroundView alloc] initWithFrame:cell.frame];
TableCellBackgroundView *selectedBackgroundView = [[TableCellBackgroundView alloc] initWithFrame:cell.frame];
selectedBackgroundView.selectedStyle = YES;
// top row, without header
BOOL header = [self tableView:aTableView heightForHeaderInSection:indexPath.section]!=0;
if (indexPath.row == 0 && (!header || tableTheme==kTableThemeSimple)) {
backgroundView.topRadius = 6;
selectedBackgroundView.topRadius = 6;
}
// bottom row
if (indexPath.row == [self tableView:aTableView numberOfRowsInSection:indexPath.section]-1) {
backgroundView.bottomRadius = 6;
selectedBackgroundView.bottomRadius = 6;
}
cell.backgroundView = backgroundView;
cell.selectedBackgroundView = selectedBackgroundView;
cell.contentView.backgroundColor = [UIColor clearColor];
cell.backgroundColor = [UIColor clearColor];
}
}
If you have a method that you call for the custom animation, when the animation is completed, or rather prior to the display of the new cell, using a subclass of UITableViewCell make a call to a method named something like - (void) fixCornersWithIndexPath: (NSIndexPath *) indexPath.
- (void) customAnimationForCellAtIndexPath: (NSIndexPath *) path {
//your animation things
CustomCell *cell = [self.tableView cellForRowAtIndexPath: indexPath];
[cell fixCornersWithIndexPath: path andRowCount: [self tableView:aTableView numberOfRowsInSection:indexPath.section]];
}
and then fix corners should be something like this:
- (void) fixCornersWithIndexPath: (NSIndexPath *) indexPath andRowCount: (NSInteger *) rowCount {
if (indexPath.row == 0 && (!header || tableTheme==kTableThemeSimple)) {
self.backgroundView.topRadius = 6;
self.selectedBackgroundView.topRadius = 6;
}
// bottom row
if (indexPath.row == rowCount-1) {
self.backgroundView.bottomRadius = 6;
self.selectedBackgroundView.bottomRadius = 6;
}
}
Hope this helps!
I think this is what you're looking for. After you've altered your model, call:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
This will cause your cellAtIndexPath method to be invoked. Send an array with a single index path of the row that's out of date (I guess for the rounded corners problem, that's either the last row after a row was removed, or the row before a newly added last row).
You can call it along with your other update methods in between begin/end updates.
The solution that worked for me is this:
Set the background in tableView:willDisplayCell:forRowAtIndexPath:
Create a convenience method called reloadBackgroundForRowAtIndexPath that calls willDisplayCell manually
When I add a row, call the convenience method after a delay to allow the table animations to finish
- (void)reloadBackgroundForRowAtIndexPath:(NSIndexPath*)indexPathToReload {
[self tableView:self.tableView willDisplayCell:[self.tableView cellForRowAtIndexPath:indexPathToReload] forRowAtIndexPath:indexPathToReload];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// Logic to determine the background image
}
// elsewhere, update the table, then
NSIndexPath *indexPathToFix = [NSIndexPath indexPathForRow:count-1 inSection:0];
// If the new row is coming out, we want to update the background right away. If the new row is going away, we want to wait to update until the animation is done.
// I don't like hard-coding the animation length here, but I don't think I have a choice.
[self performSelector:#selector(reloadBackgroundForRowAtIndexPath:) withObject:indexPathToFix afterDelay:(self.tableView.isEditing ? 0.0 : 0.2)];

UITableViewCell ShouldIndentWhenEditing Issues

Im having issues the editing mode of the tableview. When we set the table to edit mode it indents the row cells to the right for edit field on the right. I want to stop this from happening. I have set the cell.shouldIndentWhileEditing = NO; but this doesn't change anything.
Another thing to note is this cell is programmatically built on the fly e.g.
static NSString *CellIdentifier = #"ListingCustomCell";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 34) reuseIdentifier:CellIdentifier];
}
cell.shouldIndentWhileEditing = NO;
//SETUP CELL FIELDS
//return cell;
Any ideas on what im doing wrong?
Thanks
Perhaps your UITableViewDelegate is returning YES in - (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath. If the delegate method is implemented, it'll override the value on the cell.
Did you read this part of the documentation?
This property has an effect only on table views created in the grouped
style (UITableViewStyleGrouped); it has no effect on
UITableViewStylePlain table views.
Maybe that’s your case.

Problem with configuring a UITableView as a inclusive selection list

I am programing a UITableView to behave as an inclusive selection list. My table displays correctly and allows for multiple cells to be selected with check boxes. My problem is that cells which have been selected (cells contain a check mark to the right) loose their selected status when scrolled out of view (cells check mark disappears). I want the selections made to cells in the table to be preserved even if cells are scrolled out of view. Does anyone have any idea what is causing this?
Here is my code inside of my TableViewController class:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSUInteger row = [indexPath row];
cell.textLabel.text = [widgetTitles_glob objectAtIndex:row];
cell.detailTextLabel.text = #"";
cell.textLabel.textColor = [UIColor blackColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:15];
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] animated:YES];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Reflect selection in data model
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
// Reflect deselection in data model
}
}
Any help would be very much appreciated.
When you are using UITableView correctly, only as many UITableViewCell instances are allocated as are needed to fit on the screen. When you scroll down a table, and a cell disappears off the top of the screen, it is relocated to the bottom.
Your delegate method, tableView:cellForRowAtIndexPath: is responsible for setting up a cell, either creating a new one or reconfiguring a recycled one.
The proper thing to do is use an array to store your checked/unchecked values. When didSelectRowAtIndexPath: is called, you update the cell and your array. When tableView:cellForRowAtIndexPath: is called, you configure the cell based on the values in the array.
Based on your comments, you are already doing the right thing in didSelectRowAtIndexPath:; you just need to use those values when you set up the cell instance, because that cell could represent a row that has already been checked. Check the array and then set cell.accessoryType accordingly.
In cellForRowAtIndexPath: you were assigining the accessoryType as none, so whenever you scroll that delegate is called and set the accessory type as none. So you should change your code.
I have also faced this problem once; I came up with a solution as follows.
Store the indexPath.row values of selected indexPath in an array (this code should be in didSelectRowAtIndexPath delegate) if it is deselected remove from that array. In cellForRowAtIndexPath: method I have used a for loop and check if that indexPath.row is present then change it's accessory type to checkmark else none.
Thanks for your help. It actually turns out that the reason why the cells were getting reset to UITableViewCellAccessoryNone was becasue of the following line of code inside of cellForRowAtindexPath:
cell.accessoryType = UITableViewCellAccessoryNone;
Removing this has fixed the table.

Custom drawing in UITableView failing after 5 cells

I'm having a funky problem with drawing a custom view in a UITableView. The drawRect function of my custom view, called ChatBubbleView, is just drawing a rectangle of a constant size.
I have an NSLog debugging statement in the drawRect function of ChatBubbleView. The first 5 times I add a cell to the UITableView, everything draws nicely, and I see the NSLog statement trigger. After 5 however, the drawing becomes garbled, and I no longer see the debugging statement.
I'm totally baffled, and I'd appreciate any help you might provide. Thanks in advance!
Here is the function that is called when reloadData is called on the UITableView.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (forumMode) {
//This part works, ignore
} else {
ChatBubbleView* chatBubble = nil;
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier] autorelease];
// Add the bubble
chatBubble = [[[ChatBubbleView alloc] init] autorelease];
chatBubble.tag = BUBBLEVIEW_TAG;
[cell.contentView addSubview:chatBubble];
} else {
// Reuse old views
chatBubble = (ChatBubbleView*)[cell.contentView viewWithTag: BUBBLEVIEW_TAG];
}
chatBubble.labelSize = CGSizeMake(50, 18);
chatBubble.frame = CGRectMake(0, 0, chatBubble.labelSize.width, chatBubble.labelSize.height);
}
return cell;
}
EDIT: I should add that the function that reloads the UITableView looks like this:
-(IBAction) btnAdd:(id) sender {
[listOfMessages addObject:textView.text];
[self.tableView reloadData];
}
EDIT 2: I've discovered that the custom views in the cells sometimes redraw themselves when I scroll the cells in and out of view. It seems that the tableview refreshing changes the parameters of the custom view.
You say //This part works, ignore but if you are reusing the CellIdentifier it might not work as the cell that will get dequeued could be a different type of cell (that happens to have the same CellIdentifier). Make sure each cell type has a unique CellIdentifier.
To be sure, put some debugging in your if / else block to see what cell you're getting back...

What is a good way to animate UIViews inside UITableViewCell after a callback?

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.