On Jun 24, 2009, at 11:30 PM, Bryan Hansen wrote:

I'd like to add a custom button to my own custom tableview subclass that will perform an action on the tableviewcontroller class. This is pretty much identical to the way accessoryviews call a method on the tableviewdelegate when it is tapped. The difference is it will be my own button placed where I choose in the cell. The problem I'm having is figuring out how to propagate this to the tableviewcontroller. The UITableViewCell class does not hold a reference back to the tableview or controller, so I'm a little confused on the best way to set up this behavior. Can anyone offer some insight on the correct way to get a button tap in a cell to call a method on the tableviewcontroller? One that does not have bad coupling in its design?


Since this doesn't seem to have been fully addressed yet, and particularly given that there are errors and an omission in one of the responses:

There are two possible scenarios, for "static" or "replicated" content.

Static content
--------------
If you have a table view whose contents are "static" -- that is, you have a small fixed set of unique cells are simply using the table view for layout -- then it is reasonable to configure the individual cells, including your button (and perhaps to make things easiest, the table view itself), in a nib file. Broadly speaking the technique is then as follows.

The class of the File's Owner should be your table view controller class. In your table view controller class, you declare outlets for the various cells. In the nib file, you connect the outlets to the cells (and if you choose to put the table view in the nib file, connect the table view outlet to the table view), and connect the button's action to the File's Owner.

If you choose to put the table view in the nib file, you initialise the table view controller using initWithNibName:bundle: and you're pretty much done. In your tableView:cellForRowAtIndexPath: method you check for the section/row and return the appropriate cell (configured if necessary):

// Simplified code example
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

        if (indexPath.row == 0) {
                return cell0;
        }
        if (indexPath.row == 1) {
                return cell1;
        }
// (or use a case statement) etc. -- configuring cells where appropriate.


If you don't put the table view in the same nib file and don't initialise the table view controller using initWithNibName:bundle:, then you load the cell's nib files in tableView:cellForRowAtIndexPath:

// Simplified code example
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

        if (indexPath.row == 0) {
                if (cell0 == nil) {
[[NSBundle mainBundle] loadNibNamed:@"MyCell" owner:self options:nil];
                }
                return cell0;
        }
// etc. -- configuring cells where appropriate.




Replicated content
------------------
If you replicate a cell within a table view, then (assuming you want to use a nib file) the cell must go in a separate nib file so that you can load it an arbitrary number of times, as discussed here: <https://devforums.apple.com/thread/3469?start=0&tstart=0 >

To summarise the general approach, though, with a particular implementation:

In your table view controller class, declare an outlet for the custom cell and an action method for the button:
        @property (nonatomic, assign) IBOutlet <#Your table view cell class#>
                        *<#cell outlet property#>;
        - (IBAction)<#button action#>:(UIButton *)sender;

In your table view cell class, declare an outlet for the button (and any other UI elements as appropriate):
        @property (nonatomic, retain) IBOutlet UIButton *<#button property#>;

In the cell's nib file:
The class of the File's Owner should be your table view controller class.
        Set an identifier for the cell.
        Connect the appropriate outlet from File's Owner to the cell
        Connect the button's action to the File's Owner
        Connect the cell's button outlet to the button


In your table view controller subclass, then implement the tableView:cellForRowAtIndexPath: method along the following lines:

// Simplified code example
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

         static NSString *CellIdentifier = @"<#your cell's idenitifier#>";
        
<#Your table view cell class#> *cell = (<#Your table view cell class#> *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        
         if (cell == nil) {             
[[NSBundle mainBundle] loadNibNamed:@"<#Your nib file name#>" owner:self options:nil];
                 cell = <#cell outlet property#>;
                 self.<#cell outlet property#> = nil;
        }

        cell.<#button property#>.tag = indexPath.row;

        // implementation continues...


In your implementation of the action method, you can ask the button for its tag (which will indicate the row with which it's associated).

        - (IBAction)<#button action#>:(UIButton *)sender {

                NSInteger row = sender.tag;


For an example that illustrates several aspects of this approach, see:
<http://developer.apple.com/iphone/library/samplecode/TaggedLocations/index.html >




On Jun 25, 2009, at 1:16 AM, WT wrote:

I have a custom table view cell in a separate nib file whose File Owner's class is the table view controller class.
[...]
There are errors and an omission in this description:

   static NSString* cellID = @"cellID";

Note that you must specify the same identifier for the cell in the nib file.

       NSArray* nib = [[NSBundle mainBundle]
           loadNibNamed: @"CellNibName" owner: self options: nil];

       cell = (CustomCell*) [nib objectAtIndex: 0];


In general, the appropriate way to refer to an object in the nib file is using an outlet. See above and <https://devforums.apple.com/thread/3469? start=0&tstart=0> for a discussion of the technique.


2) you must pass 'self' as the owner in the - loadNibNamed:owner:options: call. This way, when the button is tapped, the action in the table view controller is triggered. Now, presumably, you know which cell is currently selected (you probably keep track of that in your table view controller class), so you always know which cell the button action came from.

There is no guarantee that if a button is tapped the corresponding row is selected (in fact it typically won't be). Moreover, *in general*, selections in a table view should be temporary, so you should actually not be keeping track of the current selection (see the iPhone UI Guidelines for details).


Alternatively, you can set the button's tag to an index that depends on the cell's index path. If your table has only one section, then
button.tag = [index_path row];
would do be sufficient. You should set the tag inside the if (cell == nil) block above, assuming that you expose the button as a cell property and assuming that you're not adding or deleting cells. If you do add or delete cells, you should set the button tag *outside* the if (cell == nil) block. Either way, you can identify which row (ie, which cell) is responsible for triggering the action by looking at the action's sender's tag.


Whether or not cells are added or deleted is not relevant.
If (as will typically be the case) cells may be *reused*, then you should set the tag for the cell *outside* of the (cell == nil) block since the identifier should be updated each time the cell is reclaimed to display a different row.



mmalc

_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to