I am currently creating a custom grid view, which means that I am creating a class that has a lot in common with UITableView
. One of the things that I want to get right is the communication of the cells and the grid view.
I was therefore wondering how a table view cell talks to its table view. For example, how does the cell notify the table view that its delete button was tapped and the cell needs to be removed from the table view?
There are several possible scenarios, but I am not sure which one is being used by Apple since the headers of UITableView
or UITableViewCell
reveal this (or am I overlooking something).
Ultimately, the goal is to let the cell and the grid view communicate in private, that is, without exposing any public methods or protocols (if this is possible).
Now a delete button might be a poor example because iOS has a built in method which allows you to delete rows and notify your datasource called:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
However, for the sake of understanding if you wanted to add a button to your tableview cell and have it perform an action that isn't in the standard iOS library you would create a delegate in your cell and set your tableview's datasource file as the delegate.
Basically you would subclass UITableViewCell like so
MyCustomCell.h
@protocol MyCustomCellDelegate;
@interface MyCustomCell : UITableViewCell
@property (nonatomic, unsafe_unretained) id <MyCustomCellDelegate> delegate; //Holds a reference to our tableView class so we can call to it.
@property (nonatomic, retain) NSIndexPath *indexPath; //Holds the indexPath of the cell so we know what cell had their delete button pressed
@end
/* Every class that has <MyCustomCellDelegate> in their .h must have these methods in them */
@protocol MyCustomCellDelegate <NSObject>
- (void)didTapDeleteButton:(MyCustomCell *)cell;
@end
MyCustomCell.m
@synthesize delegate = _delegate;
@synthesize indexPath = _indexPath;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self)
{
/* Create a button and make it call to a method in THIS class called deleteButtonTapped */
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(5, 5, 25, 25);
[button addTarget:self action:@selector(deleteButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
/**
* This is the method that is called when the button is clicked.
* All it does is call to the delegate. (Whatever class we assigned to the 'delegate' property)
*/
- (void)deleteButtonTapped:(id)sender
{
[self.delegate didTapDeleteButton:self];
}
Your TableView's datasource would look something like this.
MyDataSource.h
/* We conform to the delegate. Which basically means "Hey you know those methods that we defined in that @protocol I've got them and you can safely call to them" */
@interface MyDataSource : UIViewController <MyCustomCellDelegate, UITableViewDelegate, UITableViewDataSource>
@property (nonatomic,retain) NSArray *tableData;//We will pretend this is the table data
@property (nonatomic,retain) UITableView *tableView;// We will pretend this is the tableview
@end
MyDataSource.m
//We will pretend we synthesized and initialized the properties
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier: @"MyCustomCell"];
if (!cell)
cell = [[DownloadQueueCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: @"MyCustomCell"];
cell.delegate = self; // Make sure we set the cell delegate property to this file so that it calls to this file when the button is pressed.
cell.indexPath = indexPath;// Set the indexPath for later use so we know what row had it's button pressed.
return cell;
}
- (void)didTapDeleteButton:(MyCustomCell *)cell;
{
// From here we would likely call to the apple API to Delete a row cleanly and animated
// However, since this example is ignoring the fact that they exist
// We will remove the object from the tableData array and reload the data
[self.tableData removeObjectAtIndexPath:cell.indexPath];
[self.tableView reloadData];
}
Basically, long story short. For your gridview you would just create a delegate method that tells the user a certain button was pressed.
UITableViewCell
items are subviews of UITableView
. So you could use it to communicate between cells and tableView. In its turn UITableView
has the delegate and datasource to communicate with its controller. This might help.
I'm not sure that a private communication channel is needed.
The table view imposes a delete view adjacent to a given cell by resizing the table view cell and creating a new view in the open space.
The imposed delete view is instantiated with the table view, the index path, and the table view delegate. The delete view handles the touch and sends a message to the table view delegate including the table view and index path. The table view delegate does the work of removing the entry from the data source, animating the cell removal and refreshing the table view. Upon refresh, the table view redraws all the visible cells according to the data source.
I'm not sure if this is what you are looking for, but take a look at this question: Extend a protocol privately using a category in objective c
You can have your custom cell UIViews have a private property of the type of your Grid View. When you add these cells to your GridView, update that property to the gridView.
I have my custom grid and do it this way.
Another way is having a method in your grid to pass a cell, and that will return you the index. UITableView has those methods too. That way when a button in a cell is pressed, all you have to do is get the cell and pass it to the grid, that will return an index. With that index you access the data...
You may use categories.
You declare your private methods in a separate category, and place it to the separate file. In the implementation file of class which wants to use these private methods, you import this file with private category, and use the private methods. So the public .h of the class which uses them is left intact.
Example:
MyGridViewCell.h:
@interface MyGridViewCell : UIView
// ...
@end
MyGridViewCell.m:
@implementation MyGridViewCell : UIView
// ...
@end
Now the private methods category interface:
MyGridViewCellPrivate.h:
@interface MyGridViewCell (Private)
- (void) privateMethod1;
@end
And implementation:
MyGridViewCellPrivate.m:
@implementation MyGridViewCell (Private)
- (void) privateMethod1
{
// ...
}
@end
Header remains the same as before:
MyGridView.h:
@interface MyGridView : UIView
- (void) publicMethod1;
@end
But the implementation may use the private API:
MyGridView.m:
#import "MyGridViewCell.h"
#import "MyGridViewCellPrivate.h"
- (void) publicMethod1
{
// Use privateMethod1
}