3
\$\begingroup\$

I have a UITableview with custom cells, every cell has a button called cellButton. When the user taps the button I present an action sheet which has two buttons, buttonOne and buttonTwo. When buttonOne has been tapped I call a method with some values from the actual cell, when buttonTwo I perform a segue and prepareForSegue: method to pass data from the table view cell to another VC. I have a working solution, but I'm not sure that this is the best way because I'm passing the PFUser object from the index path to an instance variable and only use it when the user choose something in the action sheet. I never needed passing values like this, I always used directly the NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];.. way in prepareForSegue:, therefore I'm a bit confused now. Is it a correct way or can it go wrong in any case?

@property (nonatomic, strong) PFUser *userObj;

-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    CustomTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"cell"];

    PFUser *user = [self.dataSourceArray objectAtIndex:indexPath.row];
    cell.usernm.text = user.username;
    cell.userId.text = user.objectId;

    cell.cellButton.tag = indexPath.row;
    [cell.cellButton addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
    return cell;
}

- (void)buttonTapped:(id)sender {

    UIButton *button = (UIButton *)sender;
    self.userObj = [self.dataSourceArray objectAtIndex:button.tag];
   [self setupActionSheet];

}

- (void) setupActionSheet {


    AHKActionSheet *actionSheet = [[AHKActionSheet alloc] initWithTitle:NSLocalizedString(nil, nil)];

    [actionSheet addButtonWithTitle:NSLocalizedString(@"Button 1", nil)
                              image:[UIImage imageNamed:@"1"]
                               type:AHKActionSheetButtonTypeDefault
                            handler:^(AHKActionSheet *as) {

                                [Utilz checkUser:self.userObj.objectId with:self.userObj.username];

                            }];

    [actionSheet addButtonWithTitle:NSLocalizedString(@"Button 2", nil)
                              image:[UIImage imageNamed:@"2"]
                               type:AHKActionSheetButtonTypeDefault
                            handler:^(AHKActionSheet *as) {


                                [self performSegueWithIdentifier:@"presentNextView" sender:self];

                            }];

    [actionSheet show];


}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@"presentNextView"]) {
        if ([[segue destinationViewController] isKindOfClass:[ViewControllerTwo class]]) {

            ViewControllerTwo *dataToPass = [segue destinationViewController];

            dataToPass.usrStr = self.usrObj.username;
            dataToPass.objStr = self.usrObj.objectId;

        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ I have rolled back your update to your question. Please see: What you may and may not do after receiving answers - in this case, I recommend resolving the broken part of your code, and then posting a follow-on. \$\endgroup\$ – rolfl Mar 22 '15 at 17:49
  • \$\begingroup\$ @rolfl sorry, I didn't know about that question. How can I find the part you deleted? I will move it to SO. \$\endgroup\$ – gatto Mar 22 '15 at 18:25
  • 1
    \$\begingroup\$ You can click on the "edited 2 hours ago" and get the revision history... and you can access the actual source here. \$\endgroup\$ – rolfl Mar 22 '15 at 20:26
6
\$\begingroup\$

Let's look at tableView:cellForRowAtIndexPath:. This needs to be abstracted quite a bit more.

First of all, CustomTableViewCell is a bad name for a class. Every table view cell is custom. We need to spend more time working on our cell subclass, because the name is just the start.

Let's change our cell. Instead of exposing our UI elements, we should create a property for a PFUser and a delegate. We also need to create an enum in our cell's header to send to our delegate, and a protocol for our delegate to conform to.

Enum:

typedef NS_ENUM(NSInteger, MyUserCellAction) {
    MyUserCellActionCheckUser,
    MyUserCellActionPerformSegue
};

Of course, this needs more accurate, more descriptive names, but only you know what your app does, so be sure to pick some really good names. It shouldn't be "PerformSegue", but should be whatever action is the reason we're performing the segue... are we logging in?

Protocol:

@protocol MyUserCellDelegate <NSObject>

@optional - (void)myUser:(PFUser *)user didPerformAction:(MyUserCellAction)action;

@end

Again, this needs slightly better naming, because the class shouldn't be called "MyUserCell". We need a name that is more descriptive, but the protocol method should be something about like this. An optional method which passes back the user for which this action is being performed, and the action out of the enum we just defined.

@interface:

@interface MyUserCell : UITableViewCell

@property (weak) id<MyUserCellDelegate> delegate;
@property PFUser *user;

@end

And we might have other properties in here, but importantly, we should be exposing the UI elements like the buttons or various text boxes. The cell should take care of setting up its own view.

Whenever the user property is set, we should set up the dynamic parts of our UI (the labels).

- (void)setUser:(PFUser *)user {
    _user = user;
    // setup UI
}

Our cell's button should already be tied to an IBAction within the cell. Either we set the cell up in an interface builder and link it from there, or we create it entirely programmatically and add the action in init.

- (IBAction)buttonTapped:(id)sender {
    // cell handles its button being tapped
}

Here, we can preset our action sheet.

In one handler block, we'll put the following snippet:

if ([self.delegate respondsToSelector:@selector(myUser:didPerformAction:)]) {
    [self.delegate myUser:self.user didPerformAction:MyUserCellActionCheckUser];
}

And in the other, this snippet:

if ([self.delegate respondsToSelector:@selector(myUser:didPerformAction:)]) {
    [self.delegate myUser:self.user didPerformAction:MyUserCellActionPerformSegue];
}

Now, all that's left is setting something up as the delegate.

In the .h file with the @interface section, we need to declare the object as conforming to the protocol:

@interface MyDelegateClassViewController : UIViewControll <MyUserCellDelegate>

And do all of our normal stuff, but then in the .m file, we need to include the following method if we wish to handle the action of the button being pressed:

- (void)myUser:(PFUser *)user didPerformAction:(MyUserCellAction)action {
    if (action == MyUserCellActionCheckUser) {
        // do what we want
    } else if (action == MyUserCellActionPerformSegue) {
        // do the other thing we want
    }
}

And now, our cell and table and view controller aren't so tightly coupled. If we want to add more actions, we can do that. If we want to add some actions that might just update the PFUser object, the cell can handle that without calling to the delegate. If we want to use a different delegate for our even number cells than for our odd number cells, we can do that and easily get different actions despite the same set of buttons.

\$\endgroup\$
  • \$\begingroup\$ I tried it, but unfortunately I got some error and a warning. Possibly could you check my question again? I've updated it with my attempt. \$\endgroup\$ – gatto Mar 22 '15 at 16:53
  • \$\begingroup\$ @nhgrif I heard that using delegate on each cell causes memory consumption, so use tags. Is this true? \$\endgroup\$ – EI Captain v2.0 Dec 26 '16 at 5:32
  • \$\begingroup\$ @EICaptainv2.0 no \$\endgroup\$ – nhgrif Dec 26 '16 at 7:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.