iOS5 dynamically assign an action to an accessory button within UITableViewCell - iphone

I have one table view within the app that I hope multiple parts of the app would be able to use to post updates to the table view with possible actions that the user can take. One of the ways that I thought of implementing this is via a large enum, with a switch statement with the table view. In this case, the table view would be performing an action based on the enum value for the table cell. This requires knowledge of the classes involved, and seems overly complicated.
There has to be a better way. Is it possible to use selectors with a target for the UITableViewCell accessory button?
I think that for regular buttons and navbar buttons I can do something like this:
[thisIconBtn addTarget:self action:#selector(changeIconState:) forControlEvents:UIControlEventTouchUpInside];
is there an equivalent way of assigning an action to the UITableView accessory? Or should I stick with the large enum?
thank you!

Unfortunately, the accessoryAction of a UITableViewCell is deprecated as of iOS 3.0.
From the UITableViewCell Reference Docs:
accessoryAction
The selector defining the action message to invoke
when users tap the accessory view. (Deprecated in iOS 3.0. Instead use
the tableView:accessoryButtonTappedForRowWithIndexPath: for handling taps
on cells.)
However, if your accessory view inherits from UIControl (UIButton, etc), you may set a target and action through the addTarget:action:forControlEvents: method.
Something like this (modify to fit your needs):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:kCustomCellID];
if (!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCustomCellID] autorelease];
}
// Set the accessoryType to None because we are using a custom accessoryView
cell.accessoryType = UITableViewCellAccessoryNone;
// Assign a UIButton to the accessoryView cell property
cell.accessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// Set a target and selector for the accessoryView UIControlEventTouchUpInside
[(UIButton *)cell.accessoryView addTarget:self action:#selector(someAction:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Or, the more traditional route (I'm not positive, but I believe your question states that this is what you are trying to avoid. You can ignore this block of code if that is true.):
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0:
switch (indexPath.row) {
case 0:
// <statements>
break;
case 1:
// <statements>
break;
default:
// <statements>
break;
}
break;
case 1:
switch (indexPath.row) {
case 0:
// <statements>
break;
case 1:
// <statements>
break;
default:
// <statements>
break;
}
break;
default:
break;
}
}

Related

How to implement action in didSelectRowAtIndexPath of UITableview?

On my view, I used to have few buttons and each button had an action associated with it.
UIButton *testButton = [[UIButton alloc] initWithFrame:CGRectMake(120,300,90,90)];
[testButton setBackgroundImage:[UIImage imageNamed:#"test.jpg"] forState:UIControlStateNormal];
[testButton addTarget:self.view action:#selector(gotoProd:) forControlEvents:UIControlEventTouchUpInside];
[testButton addt
[scrollView testButton];
But now I am trying to replace all those buttons with the tableview with rows. I was able to populate the rows and I know one needs to use didSelectRowAtIndexPath for handling on select event of the cell. But how can I implement action:#selector(gotoProd:) in tableviews ?? Any help will be greatly appreciated.
The most straight-forward way would look like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0:
[self doRow0Action];
break;
case 1:
[self doRow1Action];
break;
// etc...
default:
break;
}
}
If you wanted to instead, you could initialize an array with SEL types:
[actionArray addObject:#selector(doRowNAction)];
then access it like this:
[self performSelector:[actionArray objectAtIndex:indexPath.row] withObject:nil];
Call
[self gotoProd:indexPath.row];
from didSelectRowAtIndexPath
and
- (void)gotoProd:(int)rowSelected {
//Check row index here and do it accordingly
}

First and last UITableViewCell keep changing while scrolling

I have a tableView with cells containing one UITextField as a subview for each cell. My problem is that when I scroll down, the text in the first cell is duplicated in the last cell. I can't for the life if me figure out why. I have tried loading the cells from different nibs, having the textFields as ivars. The UITextFields don't seem to be the problem, I'm thinking it has something to do with the tableView reusing the cells.
The textFields all have a data source that keeps track of the text within the textField and the text is reset each time the cell is shown.
Any ideas? I'd really appreciate some more answers for this question.
UPDATE 2:
This is the code I have for a custom cell, called JournalCell. Really appreciate the feedback.
I have 8 sections with 1 row each. The first 7 have a textField in them, the last is a cell acting like a button.
I'm testing for the button cell, if it matches the section (7), then it returns that cell, if not, it continues to the rest. Could this be it?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Section %i, Row %i", indexPath.section, indexPath.row);
if (indexPath.section == 7) {
static NSString *ButtonCellIdentifier = #"ButtonCellIdentifier";
UITableViewCell *buttonCell = [self.tableView dequeueReusableCellWithIdentifier:ButtonCellIdentifier];
if (buttonCell == nil) {
buttonCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ButtonCellIdentifier] autorelease];
buttonCell.selectionStyle = UITableViewCellSelectionStyleBlue;
buttonCell.accessoryType = UITableViewCellAccessoryNone;
buttonCell.textLabel.text = sClearAll;
buttonCell.textLabel.textAlignment = UITextAlignmentCenter;
buttonCell.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.8];
buttonCell.textLabel.backgroundColor = [UIColor clearColor];
}
return buttonCell;
}
static NSString *TextCellIdentifier = #"JournalCellIdentifier";
JournalCell *cell = (JournalCell *)[self.tableView dequeueReusableCellWithIdentifier:TextCellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"JournalCell" owner:self options:nil];
cell = customCell;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryNone;
cell.textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
cell.textField.returnKeyType = UIReturnKeyNext;
cell.textField.clearButtonMode = UITextFieldViewModeWhileEditing;
}
switch (indexPath.section) {
case 0:
switch (indexPath.row) {
case 0:
cell.textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.authorTextField = cell.textField;
self.authorTextField.text = [self.textFieldDictionary objectForKey:#"author"];
NSLog(#"Reading Author:%#", [self.textFieldDictionary objectForKey:#"author"]);
break;
}
break;
case 1:
switch (indexPath.row) {
case 0:
self.yearTextField = cell.textField;
self.yearTextField.text = [self.textFieldDictionary objectForKey:#"year"];
NSLog(#"Reading Year:%#", [self.textFieldDictionary objectForKey:#"year"]);
break;
}
break;
case 2:
switch (indexPath.row) {
case 0:
self.volumeTextField = cell.textField;
self.volumeTextField.text = [self.textFieldDictionary objectForKey:#"volume"];
NSLog(#"Reading Volume:%#", [self.textFieldDictionary objectForKey:#"volume"]);
break;
}
break;
case 3:
switch (indexPath.row) {
case 0:
self.articleTextField = cell.textField;
self.articleTextField.text = [self.textFieldDictionary objectForKey:#"article"];
NSLog(#"Reading Article:%#", [self.textFieldDictionary objectForKey:#"article"]);
break;
}
break;
default:
break;
}
return cell;
}
As you guessed, the issue is most likely coming from the conventional reuse of cells in a UITableView. Create an NSMutableDictionary as a property of your class, and whenever a UITextField is finished editing, set the value of it to a key in your dictionary.
Then, in your cellForRowAtIndexPath method, load the value of the corresponding key in the dictionary.
It would be most ideal to name each key in your dictionary with the format [indexPath section], [indexPath row], as this will aid in the setting and getting of values.
Hope this helps.
Canada Dev's answer will probably solve your issue, but it's only really a stop-gap rather than a long-term repair.
You should avoid adding subviews in the cellForRowAtIndexPath: method.
You should instead create a custom UITableViewCell (typically by subclassing UITableViewCell) and using that - otherwise you're adding/removing subviews from cells whenever they are dequeued.
If you're not sure what dequeuing of cells actually means, you should probably read up Apple's TableView documentation. Basically, use a UITableViewCell subclass, and you'll be in a much better position than trying to add all your views in your cellForRow method.
Do you really need to save each cell and textField to your viewController and then generate the cell out of this by inserting parts from other cell?
maybe you can save the whole cell in a property (e.g. self.authorCell) and then simply display this cell:
if (indexPath.section == 0) {
return self.authorCell; // or cell = self.authorCell
}
and you can still access the textField by accessing the cell property:
self.authorCell.myTextField
// EDIT: see the Apple documentation for "The Technique for Static Row Content"
Try inserting this right after allocing the cell and before doing any drawing/labeling/etc.
cell.clearsContextBeforeDrawing = YES;
cell.textLabel.text = nil;
cell.detailTextLabel.text = nil;
for (UIView *view in cell.contentView.subviews) {
[view removeFromSuperview];
}

How to get indexPath.row/path When I click the Switch in a row?

I create tow or more custom cell
each cell has a switch inside
How can I know which switch in the row I click
ex.I click the switch in row 3,than It will return indexPath.row = 3
and also the switch status is on or off ?
which void I should put in ?
I know there is a way can get indexpath return by:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
But I don't want to touch the row,just the switch ...
Oh I do some research but it just be a little different
How can keep track of the index path of a button in a tableview cell?
any ideas ?
I wish I can post my screen shot,but not I am a new user
the system doesn't allow me to do this
When you create the switch, set up a target and action for it:
[mySwitch addTarget:self action:#selector(switchToggled:) forControlEvents:UIControlEventValueChanged];
Here, the target self is your UITableViewController, in which you'll need to implement the method (void) switchToggled:(id)sender to handle the control event. The switch will automatically send itself to the switchToggled method as the sender when it is toggled. Like this (modified from the UIButton example you linked):
- (void)switchToggled:(id)sender {
UISwitch *theSwitch = (UISwitch *)sender;
UITableViewCell *cell = (UITableViewCell *)theSwitch.superview;
UITableView *tableView = (UITableView *)cell.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
if(theSwitch.on) {
// switch turned on
}
else {
// switch turned off
}
}
When button is created
button.tag = [indexPath row]
When button is selected, pull the row out of the tag
int myRow = [(UIButton *)sender tag];

Passing a value back from a view

Hey everybody, I had a question about editing the value of a table cell and returning the edited value back to the original cell.
I have a UITableView which contains 5 sections of 1 row each. Within cellForRowAtIndexPath I have the following:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
NSString *fieldTitle;
switch (indexPath.section)
{
case 0:
fieldTitle = #"First Name";
break;
case 1:
fieldTitle = #"Last Name";
break;
case 2:
fieldTitle = #"Company";
break;
case 3:
fieldTitle = #"Email Address";
break;
case 4:
fieldTitle = #"Password";
break;
}
cell.textLabel.text = fieldTitle;
When a row is clicked, didSelectRowAtIndexPath is fired as follows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
AddField *addField = [[AddField alloc] initWithStyle:UITableViewStyleGrouped];
addField.field = cell.textLabel.text;
addField.title = cell.textLabel.text;
addField.value = cell.detailTextLabel.text;
[self.navigationController pushViewController:addField animated:YES];
}
This sets up another UITableView that contains one section and one row. This table utilizes a custom cell I wrote that contains a text field the user can edit. There is a done button the user can click to go back to the previous view.
My question is this: How do I get the value the user entered in the AddField view to appear in the detailText label on the selected table cell in the previous view? This functionality would be very similar to adding the title of a new event in the iPhone's native calendar application.
Thanks for any help I can get, and let me know if you need more information.
Have you tried calling reloadData on the original tableView? Of course you would need chnage your code so that fieldTitle to get its values from an array of NSStrings......and set the tableview datasource to that array....
You would use delegates for this. Declare a protocol which is your cell delegate as follows (from memory so apologies if it doesn't compile):
#protocol CellDelegate
#required
- (void) fieldValueChanged:(AddField *)field;
#end
Add an "id delegate" to your custom cell class, and add a property (nonatomic, assign) and synthesise it.
Then when you setup your cell in didSelectRowAtIndexPath you do the following:
[cell setDelegate:self];
Now in you view controller you simply implement that protocol (CellDelegate), implement the method fieldValueChanged.... this will get called by the cell.
Now in the cell when the value is changed simply call [delegate fieldValueChanged:addfield];
NOTE: really you should be setting up the AddField WITHIN the custom cell class you write...

Selecting a default item when using UITableViewController as a checkbox list

In a lot of iPhone apps, I see a UITableViewController being used as a checkbox list. (See, for an example of what I mean, Auto-Lock under Settings)
While trying to implement this myself, I had to jump through a lot of hoops in order to have an item selected programmatically by default (ie., the current value for what the list represents). The best I've been able to come up with is by overriding the viewDidAppear method in my view controller class:
- (void)viewDidAppear:(BOOL)animated {
NSInteger row = 0;
// loop through my list of items to determine the row matching the current setting
for (NSString *item in statusItems) {
if ([item isEqualToString:currentStatus]) {
break;
}
++row;
}
// fetch the array of visible cells, get cell matching my row and set the
// accessory type
NSArray *arr = [self.tableView visibleCells];
NSIndexPath *ip = [self.tableView indexPathForCell:[arr objectAtIndex:row]];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:ip];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.lastIndexPath = ip;
[super viewDidAppear:animated];
}
Is this the best/only/easiest way to get a reference to a particular cell and indexPath if I want to mark a row by default?
In order to display the status items, you have to implement tableView:cellForRowAtIndexPath: anyway, don't you? So, why not just set the accessory type of the cell before returning the cell, like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// dequeue or create cell as usual
// get the status item (assuming you have a statusItems array, which appears in your sample code)
NSString* statusItem = [statusItems objectAtIndex:indexPath.row];
cell.text = statusItem;
// set the appropriate accessory type
if([statusItem isEqualToString:currentStatus]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Your code is extremely fragile, especially because you use [self.tableView visibleCells]. What if there are more status items than rows fitting on the screen (as the name suggests, visibleCells only returns the currently visible cells of the table view)?