patternMinor
Retrieving values from UITableViewCell
Viewed 0 times
valuesretrievingfromuitableviewcell
Problem
I have a
```
@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;
}
UIButton button = (UIButton )sender;
self.userObj = [self.dataSourceArray objectAtIndex:button.tag];
[self setupActionSheet];
}
AHKActionSheet *actionSheet = [[AHKActionSheet alloc] initWithTitle:NSLocalizedString(nil, nil)];
[actionSheet addButtonWithTitle:NSLocalizedString(@"Button 1", nil)
image:[UIImage imageNamed:@"1"]
type:AHKActionSheetButtonTypeDefault
handler:^
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:^
Solution
Let's look at
First of all,
Let's change our cell. Instead of exposing our UI elements, we should create a property for a
Enum:
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:
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
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
Our cell's button should already be tied to an
Here, we can preset our action sheet.
In one handler block, we'll put the following snippet:
And in the other, this snippet:
Now, all that's left is setting something up as the delegate.
In the
And do all of our normal stuff, but then in the
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
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
@optional - (void)myUser:(PFUser *)user didPerformAction:(MyUserCellAction)action;
@endAgain, 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 delegate;
@property PFUser *user;
@endAnd 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 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.Code Snippets
typedef NS_ENUM(NSInteger, MyUserCellAction) {
MyUserCellActionCheckUser,
MyUserCellActionPerformSegue
};@protocol MyUserCellDelegate <NSObject>
@optional - (void)myUser:(PFUser *)user didPerformAction:(MyUserCellAction)action;
@end@interface MyUserCell : UITableViewCell
@property (weak) id<MyUserCellDelegate> delegate;
@property PFUser *user;
@end- (void)setUser:(PFUser *)user {
_user = user;
// setup UI
}- (IBAction)buttonTapped:(id)sender {
// cell handles its button being tapped
}Context
StackExchange Code Review Q#84615, answer score: 6
Revisions (0)
No revisions yet.