IPhone UITableView suppress disclosure-button when Delete Button is displayed - iphone

I have a UITableView that has a disclosure button on every row. When the table is put into edit mode and the the Deletion control is pressed ("-" sign), the Delete Button shows, however the disclosure button is not replaced, but instead just slides to the left of the delete button.
The apple UITableView guide explains the delegates for everything it seems except for the delegate that is called when the "-" sign is pressed, but before the delete button is displayed.
I would just like to suppress the disclosure indicator while the delete button is shown.
I'm guessing that I am missing something... I have set the setHidesAccessoryWhenEditing:NO
on the cells of the table so that the indicator is displayed to indicate to the user that if they select the row, they can edit it...
The behavior I am trying to copy is done in the contacts app when a contact is edited. Any help would be greatly appreciated...
Thanks, Greg

The standard way to do this is to use cell.hidesAccessoryWhenEditing = YES, and that editing is a modal action in which navigation is typically disabled.
The Contacts application actually uses custom table cells, and I wouldn't be surprised if it didn't so much use an accessory as have an image located on the cell's right edge, judging by its behaviour.
If you want to know when the delete button appears, I'd suggest that you try installing a Key-Value observer on the cell's showingDeleteConfirmation property, like so:
[cell addObserver: self forKeyPath: #"showingDeleteConfirmation"
options: NSKeyValueObservingOptionNew context: NULL];
Then you implement the observer callback method:
- (void)observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
if ( [keyPath isEqualToString: #"showingDeleteConfirmation"] )
{
UITableViewCell * cell = (UITableViewCell *) object;
BOOL isShowing = [[change objectForKey: NSKeyValueChangeNewKey] boolValue];
if ( isShowing == NO )
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
else
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
I should note that I'm not sure whether the UITableViewCell class is KVO-compliant for this property, but it's got to be worth a try…

Or when you are creating your cell you can define the accessory type: cell.editingAccessoryType = UITableViewCellAccessoryNone;

The disclosure indicator is managed by tableView:accessoryTypeForRowWithIndexPath: so maybe you could change the accessory type while in editing mode.
I believe there's a tableView:accessoryButtonTappedForRowWithIndexPath:, maybe there you can note that you're going to edit mode and then change what the tableView:accessoryTypeForRowWithIndexPath: returns for each row - no accessory when in edit mode.
I would give it a try.
Here is a calling sequence (not sure if that helps) description/tutorial/examples from Apple reference about how to go about Inserting and Deleting Rows in Editing Mode.

My comment to Jim's solution didn't come across very well... Here is the version that solved the problem for me... Thanks again Jim!
- (void)observeValueForKeyPath: (NSString *) keyPath ofObject: (id) object
change: (NSDictionary *) change context: (void *) context
{
UITableViewCell * cell = object;
if ( [keyPath isEqualToString: #"showingDeleteConfirmation"] )
{
BOOL isShowing = [[change objectForKey: NSKeyValueChangeNewKey] boolValue];
if ( !isShowing )
{
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
}

Related

UITableViewCell selector setSelected:animated: gets called many times?

I've discovered a strange behavior with setSelected:animated: in my custom UITableViewCell class. I discovered that this function gets called multiple times if I click on a cell in my table. I am wondering if this is normal behavior or a bug in my code.
To help with debugging, I've modified the setSelected:animated: function in my custom UITableViewCell class implementation as such:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
if (selected)
NSLog(#"Yes %X", &self);
else
NSLog(#"No %X", &self);
}
If I click on a cell in the simulator, here is what I get in the console:
2011-03-22 22:05:26.963 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:05:26.964 marketPulse[3294:207] Yes BFFFDE30
You would think that I would get only 1 entry, since I only clicked on 1 cell.
And if I click on a different cell after that:
2011-03-22 22:07:11.014 marketPulse[3294:207] No BFFFD890
2011-03-22 22:07:11.016 marketPulse[3294:207] No BFFFDD00
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDE30
If I click on the same cell 2 times in a row, I get more than 2 Yes:
2011-03-22 22:08:41.067 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:08:41.068 marketPulse[3294:207] Yes BFFFDE30
2011-03-22 22:08:41.069 marketPulse[3294:207] Yes BFFFDE30
The more times I click the same cell, the more Yes I will get, and if I click on a different cell after that, I'll get a lot of No
I put a breakpoint before the NSLog, and looking at the debugger, it seems that all the repeated calls are coming from the same object.
Here is a part of my tableView:cellForRowAtIndexPath: function so you can see how my cells are being treated:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ContentCellIdentifier = #"newsTableCellContent";
UITableViewCell *cell;
//index of cell data in tableData
NSUInteger index = indexPath.row / 2;
...
//content of story
else if( [indexPath row] % 2 == 1 ) {
cell = [tableView dequeueReusableCellWithIdentifier:ContentCellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle]
loadNibNamed:#"newsTableCells"
owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ( [currentObject isKindOfClass:[newsTableCellContent class]] ) {
cell = currentObject;
break;
}
}
}
((newsTableCellContent *)cell).content.text = [[tableData objectAtIndex:index] description];
}
return cell;
}
Everything works fine so its hard to tell if the repeat calls to setSelected:animated: are intentional or not. If this is normal operation, I can make do with another method, but I would just like to know if this is suppose to happen or not.
Thanks
What's going on is simply that the UITableView keeps track of which cells are selected in the table.
Since cells are reused when you scroll through a large table view, the table view has to keep the list of selected cells separate. Not only that, but whenever it reuses a cell it has to set its selected property, because it may be using an old, invalid selected state from a previous incarnation.
When you tap a cell, several things happen: the previously selected cell is deselected (using setSelected:). The new cell is highlighted. It's de-highlighted (at least if you tap, instead of holding your finger down), and the setSelected: method is called because the new cell was selected. That's one.
The second call is a delayed perform call, possibly from a point where the table view didn't yet know what the final state of the table would be. This call goes to _selectAllSelectedRows, which, as the name suggests, calls 'setSelected:animated:' on all selected rows. That's the second call. The reason for this is most likely to address potential issues due to the the table view being in a "transition", but who knows.
Whether it's a bug or not is up for interpretation. A fix for the duplicate calls is to simply do:
if (self.selected == selected) return;
right before the call to super (you do not have to call super if self.selected == selected).
This is a normal behavior if you're using iPad. (it is only called once on iPhone).
In order to stop getting multiple "setSelected:YES" or multiple "setSelected:NO", all you have to do is this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Now, 1 click on any cell gives you:
1 entry of setSelected:YES animated:NO
1 entry of tableView: didSelectRowAtIndexPath:
1 entry of setSelected:NO animated:YES
So, calls are now stable regardless of what you do.
Ideally you should not be calling setSelected from anywhere in your code.
UIKit will take care of calling it.
If you want to show a cell/row as selected in cellForRowAtIndexPath method simply call
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None)
for that specific indexPath.
Again never ever call setSelected explicitly unless you really mean to.
It should definitely be called when table is scrolled. Cells are reused, that means, if you scroll cells in invisible areas will be reused and reinitialized, including the call to setSelected, which is basically a lightweight property setter.
If you really want to see what's happening, add a NSLog to tableView:cellForRowAtIndexPath: which will log indexPath and the returned cell.
The entire log should give you a good understanding what happens inside and why.
I suppose it will be something like this (Clicked on IndexPath 1:1)
Give me cell on 1:0 (previously selected cell).
Deselect 1:0
Give me cell on 1:0 again (updated after deselection)
Deselect 1:0 (update selected flag on this cell and trigger animation)
Give me cell on 1:1
Select 1:1
Give me cell on 1:1 again (updated after selection)
Select 1:1 (update selected flag on this cell and trigger animation)
Clicking on a selected cell again is only slightly different - instead of triggering unselecting, it triggers another update.

Customizing table cell in ABPeoplePickerNavigationController

I've spent some time searching for this answer on SO, but couldn't find it, so here goes:
I'm starting with an ABPeoplePickerNavigationController, and when a user taps a person, they'll be taken to an ABPersonViewController where they'll be able to select phone numbers and email addresses. After they're finished with the ABPersonViewController, they'll be taken back to the ABPeoplePickerNavigationController. Pretty simple stuff.
What I want is to add a detailLabel to the table cell they selected in ABPeoplePickerNavigationController after they chose a phone number or an email address. Something like "Email and phone number chosen" or "Phone number chosen".
Apple's documentation says:
You should not need to subclass these controllers; the expected way to modify their behavior is by your implementation of their delegate.
The delegate methods provided won't handle this. Is there any way to accomplish this without subclassing myself? And, if I do have to subclass ABPeoplePickerNavigationController, which method would I override to update the detailLabel?
Thanks!
This bit of code seems to work for me, it grabs the cell when the user selects a person and adds a check mark. I'm guessing you can tweak the cell in other ways at this point as well.
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person{
UIView *view = peoplePicker.topViewController.view;
UITableView *tableView = nil;
for(UIView *uv in view.subviews)
{
if([uv isKindOfClass:[UITableView class]])
{
tableView = (UITableView*)uv;
break;
}
}
if(tableView != nil)
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:[tableView indexPathForSelectedRow]];
if(cell.accessoryType == UITableViewCellAccessoryNone){
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone;
}
[cell setSelected:NO animated:YES];
}
return NO;
}
I have idea of how to do it, i think it will be helpful to you , but never implemented like this.
First you have to go with custom table . For that table you can give all the contact names from your addressbook. you can use http://developer.apple.com/library/mac/#documentation/userexperience/Reference/AddressBook/Classes/ABAddressBook_Class/Reference/Reference.html
just go through it you can understand .
you have to use these methods.
1) - (NSArray *)people
you will get all people records into returned array. each record will have unique id , you have to retrieve it
ABRecord rec = [returnedArray objectAtIndex:0];
NSString *pid = rec.uniqueId
-(NSString *) uniqueId ( this is ABRecord property method )
once you got it you can retireve from your array what you want by using that recordid/ unique id .

UISwitch and UITableViewCell iOS4

Caveat: I have looked for the answer to my question and several come close, but I'm still missing something. Here is the scenario:
I want a way to create UITableViewCells that contain UISwitches dynamically at run time, based on the data in a table (which I can do). The problem becomes connecting the switches such that I can get their value when that view is changed (navigated away, closed, etc). I have tried to use the events UIControlEventValueChanged to be notified, but have failed to specify it correctly, because it dumps when that switch is tapped. Also, there doesn't seem to be any way to uniquely identify the switch so that if all the events are handled by a single routine (ideal), I can't tell them apart.
So...
If I have a UITableView:
#interface RootViewController : UITableViewController
{
UISwitch * autoLockSwitch;
}
#property (nonatomic, retain) UISwitch * autoLockSwitch;
-(void) switchFlipState: (id) sender;
#end
// the .m file:
#implementation RootViewController
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
int row = 0;
NSString * label = nil;
TableCellDef_t * cell_def = nil;
row = indexPath.row;
cell_def = &mainMenuTableCellsDef[ row ];
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
label = (NSString *) mainMenuTableCellsDef[indexPath.row].text;
[cell.textLabel setText:(NSString *) mainMenuItemStrings[ indexPath.row ]];
if (cell_def->isSpecial) // call special func/method to add switch et al to cell.
{
(*cell_def->isSpecial)(cell ); // add switch, button, etc.
}
else
{
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
}
and this is the 'special' function:
-(void) autoLockSpecialItem :(UITableViewCell *) cell
{
autoLockSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
[autoLockSwitch addTarget:self action:#selector(switchFlipState:) forControlEvents:UIControlEventValueChanged ];
[cell addSubview:autoLockSwitch];
cell.accessoryView = autoLockSwitch;
}
and finally:
-(void) switchFlipState: (id) sender
{
NSLog(#"FLIPPED");
}
==============================================================
Questions:
Why would it crash (bad selector) when the switch was tapped? I believe that my code follows all the example code that I have seen, but obviously something is wrong.
I cannot put a instance method into a table as a function pointer; and it doesn't seem to like a class method either. If I make it a 'C/C++' function, how do I get access to the class/instance member variables? That is, if I want to put a call to autoLockSpecialItem into a static table (or reasonable facsimile) such that I can get autoLockSwitch member variable? If I make it a class method and the autoLockSwitch var a static, will that be valid?
More simply: how do I connect the UIControlEventValueChanged to my view (I have tried and failed) and can I differentiate at runtime within the event handler which switch has changed?
Is there a better way? I cannot believe that I am the first person to have to solve this type of problem.
Apologies for the length, appreciation for attention and grateful for any and all help.
:bp:
Don't know about why your method isn't connected, but a simple way to "differentiate at runtime within the event handler which switch has changed" is to take the (id)sender given to your event handler, walk your tableview, and compare the sender to any switches, if present, in each table item. If that's too slow, a hash table connecting senders to table cells, or something like that, is a possible optimization.
If you want to use C function pointers, you need to pass the object to the function to use it to call the object's property accessor methods within the function. (Or you could assign the object to a global variable if it's clearly a singleton, but that's a very politically incorrect answer.)
First, and easy way to define your different switches would be defining their tag based on the row number. When one of the switches is tapped you can access sender.tag to get the row number this way.
Also, you should probably be adding the switch the the cells content view, not the actual cell, [cell.contentView addSubview:autoLockSwitch]. Also the frame does need to be set (note CGRectZero, cocoa will ignore the width and height but uses the x,y coords to define where you want the switch in the cell.

Properly setting up willSelectRowAtIndexPath and didSelectRowAtIndexPath to send cell selections

Feel like I'm going a bit nutty here. I have a detail view with a few stand-alone UITextFields, a few UITextFields in UITAbleViewCells, and one single UITableViewCell that will be used to hold notes, if there are any. I only want this cell selectable when I am in edit mode. When I am not in edit mode, I do not want to be able to select it. Selecting the cell (while in edit mode) will fire a method that will init a new view. I know this is very easy, but I am missing something somewhere.
Here are the current selection methods I am using:
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.editing) {
NSLog(#"Returning nil, not in edit mode");
return nil;
}
NSLog(#"Cell will be selected, not in edit mode");
if (indexPath.section == 0) {
NSLog(#"Comments cell will be selected");
return indexPath;
}
return nil;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.editing) {
NSLog(#"Not in edit mode. Should not have made it this far.");
return;
}
if (indexPath.section == 0)
[self pushCommentsView];
else
return;
}
My problem is really 2 fold;
1) Even when I'm not in edit mode, and I know I am returning nil (due to the NSLog message), I can still select the row (it flashes blue). From my understanding of the willSelectRowAtIndexPath method, this shouldn't be happening. Maybe I am wrong about this?
2) When I enter edit mode, I can't select anything at all. the willSelectRowAtIndexPath method never fires, and neither does the didSelectRowAtIndexPath. The only thing I am doing in the setEditing method, is hiding the back button while editing, and assigning firstResponder to the top textField to get the keyboard to pop up. I thought maybe the first responder was getting in the way of the click (which would be dumb), but even with that commented out, I cannot perform the cell selection during editing.
Good lord I am an idiot. I never added these lines:
self.tableView.allowsSelection = NO; // Keeps cells from being selectable while not editing. No more blue flash.
self.tableView.allowsSelectionDuringEditing = YES; // Allows cells to be selectable during edit mode.
Sorry for the garbage question.
The documentation notes that tableView:willSelectRowAtIndexPath: isn't called when in editing mode. In addition, the blue flash will happen even if you cancel the selection. From the documentation:
This method is not called until users touch a row and then lift their finger; the row isn't selected until then, although it is highlighted on touch-down. You can use UITableViewCellSelectionStyleNone to disable the appearance of the cell highlight on touch-down. This method isn’t called when the editing property of the table is set to YES (that is, the table view is in editing mode).

Wipe a delete button out of table view cell

when pressing a row delete button on a table view, I do some validation, and if the user chooses to cancel the operation it all should rollback. Not only want to keep that row (what is happening), but also make disappear the delete button leaving only the "-" round button. How can I do that?
once again, thank you.
Assuming you are implementing your validations in tableView:commitEditingStyle:forRowAtIndexPath: method of your UITableViewDatasource protocol object, you should be able to set the editingAccessoryType and editingAccessoryView on the cell.
//After validation fails....
UITableViewCell *aCell;
aCell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
// validations are done and you need to ignore the delete
if ( aCell.showingDeleteConfirmation ){
aCell.editingAccessoryView = nil;
aCell.editingAccessoryType = UITableViewCellAccessoryNone;
}
If you want, you can wrap the changes in an animation block to animate the change.
Alternatively, you could toggle the editing state of the cell.
//After validation fails....
UITableViewCell *aCell;
aCell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if ( aCell.showingDeleteConfirmation ){
aCell.editing = NO;
aCell.editingAccessoryView = nil;
aCell.editing = YES;
}