How to hook a UITableViewCell/UICollectionViewCell's init method? - ios

We use UITableViewCell like this.
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerNib: [UINib nibWithNibName: Cell bundle: nil] forCellReuseIdentifier: kIdentifier];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Cell *cell = [tableView dequeueReusableCellWithIdentifier: kIdentifier forIndexPath: indexPath];
return cell;
}
When cells are born with some properties(tag), how to get cell's - init method, customize it, and tag the cell?
As I did not see any chance while calling the relative methods.
So how to hook a UITableViewCell/UICollectionViewCell's init method?
Here is a situation:
There are two pages. The cell has a page tag.
Sure, I can add property. Just go a litter farther.

I would recommend creating a simple subclass of UITableViewCell. This way you can create customized table cells with whatever you would like the cell to contain "during" the initialization of the cell. Then you could set your nib file class to, for this example, CustomTableViewCell.
Then, like you already have shown, you can just create your customized cells from your reuseIdentifier:
Additionally, you can intercept the other built in methods awakeFromNib and even prepareForReuse for further customization.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: kIdentifier forIndexPath: indexPath];
// Do anything else here you would like.
// [cell someCustomMethod];
return cell;
}
.h
#import <UIKit/UIKit.h>
#interface CustomTableViewCell : UITableViewCell
- (void)someCustomMethod;
...
#property (nonatomic, nullable) <Some class you want> *somePropertyName;
...
#end
.m
#import "CustomTableViewCell.h"
#implementation CustomTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
// Do whatever you would like to do here :)
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code. Do whatever you like here as well :)
}
- (void)prepareForReuse {
[super prepareForReuse];
// And here.. :)
}
#end

init isn't really helpful, since cells are created only rarely and then reused.
That said, when cells are initially created, you can intercept that by overloading awakeFromNib. When they're reused later, prepareForReuse is called.
Don't forget to call the super implementations in both methods.

Related

Call function from UIButton in UICellView

I have UITableView with custom cells. On cell I have a buttons, and a method in my viewController (which contains UITableView)
My button click realisation is inside my myCustomCell class.
And the question is - What is the simplest way to call viewController method from myCustomCell?
I thought about delegates and NSNotificationCenter. But maybe there is another way.
EDIT
Put following lines in your myCustomCell.h
#protocol ButtonTapDelegate
- (void) buttonDidTap:(UIButton*) button;
#end
#property (nonatomic, weak) NSObject<ButtonTapDelegate> *vs_delegate;
-(void) buttonIsPressed;
in your myCustomCell.m
#synthesize vs_delegate;
-(void) buttonIsPressed:(UIButton*)button {
if([delegate conformsToProtocol:#protocol(ButtonTapDelegate)] && [delegate respondsToSelector:#selector(buttonDidTap:)]) {
[vs_delegate buttonDidTap:button];
}
}
In your viewController.h
myViewController : UIViewController <ButtonTapDelegate>
In your viewController.m, inside Method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
[cell set.Vs_delegate:self];
[cell.button setTag:indexPath.row];
[cell.button addTarget:self action:#selector(buttonIsPressed) forControlEvents:UIControlEventTouchUpInside];
[cell.button buttonIsPressed:indexPath.row];
Put following method inside ViewController.m
- (void) buttonDidTap:(UIButton*) button {
// You have index, by Button's tag.
}
Most efficient and clean way to do this using blocks.
Declare a block property in your cell either TableViewCell or CollectionViewCell.
#property (nonatomic, copy) void(^buttonClickedAtIndexPath)(NSIndexPath *indexPath);
Declare button's action in Cell itself and Call above block in button click event.
-(IBAction)buttonClicked:(id)sender {
// get indexPath here, which will be indexPath of cell.
// you need to set button's tag as indexPath.row
if(self.buttonClickedAtIndexPath) {
self.buttonClickedAtIndexPath (indexPath);
}
}
In your ViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = // configure cell here.
cell.button.tag = indexPath.row;
cell.buttonClickedAtIndexPath = ^(NSIndexPath *indexPath){
// you can do anything here.
// Call any method using self OR
// perform segue
}
}
If you've CollectionView inside TableViewCell then same things applies.
Make a class MyCollectionViewCell subclassing UICollectionViewCell.
Declare block property in MyCollectionViewCell.
Handle all the events in MyCollectionViewCell (including display data, delegate, datasource for collectionView).
Call a block from MyCollectionViewCell on button click.
Declare a property of MyCollectionViewCell in your TableViewCell.
In your controller's cellForRowAtIndexPath do something like this.
============================================================
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = // configure cell here.
cell.mycollectionView.buttonClickedAtIndexPath = ^(NSIndexPath *indexPath){
// you can do anything here.
// Call any method using self OR
// perform segue
}
}

Embed UITableView in UITableViewCell in iOS 9, Xcode 7 and Storyboards

I have two UITableViews using Storyboards in Xcode 7. I've set the delegate and dataSource using the Connections Inspector for both table views.
Let the first table view be the main table view and let the table views within each cell of the main table view be the detail table views with cell identifiers named appropriately and respectively.
When [tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath] executes, it immediately calls its dataSource method -cellForRowAtIndexPath: for DetailCell preventing me from setting a custom instance variable in time to add the appropriate data to each cell.
The following is a simplified example marked using comments.
MainTableViewController:
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
return cell;
}
...
#end
DetailTableViewCell (contains a UITableView and implements appropriate protocols):
#interface DetailTableViewCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UILabel *nameLabel;
#property (nonatomic, weak) IBOutlet UITableView *detailTableView;
#property (nonatomic, strong) CustomObj *additionalInfo;
#end
#implementation DetailTableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Instantiate detail ...
cell.detailLabel.text = self.additionalInfo.text;
// Problem!
// self.additionalInfo == nil thus we cannot set a value to the label.
return cell;
}
...
#end
The problem is when the detail -cellForRowAtIndexPath: method is called, I haven't had a chance to set a value for its dataSource, in this case, additionalInfo.
There are many possible ways to fix your problem, but first I would say that, your design seems not a good one, A UItableViewCell has another UITableView, and another UItableViewCell inside this UITableView? Why you do this? Just use one UITableView and put all of your views into one UItableViewCell as subViews should be enough.
Now get to your problem:
I would suggest not to use IBOutlet for setting up your delegate and dataSource, use code. This can give you a chance to delay setting the dataSource and delgate when you are ready. Once you think it's the proper time, just call [cell.detailTableView reloadData] will trigger your DetailTableViewCell to invoke cellForRowAtIndexPath
#implementation MainTableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Keep in mind the following two (2) lines are set using the Connections Inspector
//cell.detailTableView.dataSource = cell;
//cell.detailTableView.delegate = cell;
// Stepping over the following line will jump to the
// other `-cellForRowAtIndexPath:` (below) used to set
// the detail info.
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
CustomObj *obj = self.mainData[indexPath.row];
cell.nameLabel.text = obj.name;
cell.additionalInfo = obj.additionalInfo; // This line is not set before instantiation begins for the detail table view...
// setup dataSource and delegate now
cell.detailTableView.dataSource = cell;
cell.detailTableView.delegate = cell;
// call reloadData whenever you think is proper
[cell.detailTableView reloadData];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = nil;
//Check this call is for which table view.
if(tableView == detailTableView) {
cell = (MainTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
// Do any additional setup you want with MainCell
} else {
cell = (DetailTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"DetailCell" forIndexPath:indexPath];
// Do any additional setup you want with DetailCell
}
return cell;
}

UITableView receives custom UITableViewCell but nothing is shown

I'm testing UITableView with a custom TableViewCell. The custom cell is designed in a .xib file and has its own class called cell, which is a subclass of UITableViewCell:
Cell.m
#import "Cell.h"
#implementation Cell
- (void)awakeFromNib
{
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
return self;
}
#end
The ViewController class has the UITableView called table in it. It is also Delegate and Datasource for it. Both is set in the Storyboard. Its code looks like this:
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.table registerClass:[Cell class] forCellReuseIdentifier:#"cell" ];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [self.table dequeueReusableCellWithIdentifier:#"cell"];
return cell;
}
#end
If I run the app, I can see the table and it has the right number of cells. But the custom cell is not shown. Each cell of the table is just white.
I read a couple of post on issues like this but no one helped me. Different Tutorials say it should work like this but it doesn't. I think there may is a stupid mistake in it. Thanks for your help.
You said you're using xib files to instantiate your cells, but you're registering a class instead of xib with this line
[self.table registerClass:[Cell class] forCellReuseIdentifier:#"cell" ];
try using registerNib:forCellReuseIdentifier: instead
I'm guessing You forget to set your custom class name in the *.xib file.
Set Custom Class property to "cell" in *.xib properties. Please check that property.

How to UIButton state in UITableViewCell?

Is there anyway to control UIButton state (enable/disable button) in UITableViewCell. My problem is my UIButton in cell is made in storyboard using viewWithTag. I've been spending quite a lot of time to sort it out but no luck. People mostly sort out the problem by programmatically assigning the tag for the button with cell indexPath.
I'm aware of that the table will reuse the cell, but I just want to ask if there is another hacky way to sort out my issue. If it is impossible, I might have to create the button programmatically.
You could loop through all the subviews of the cell and check if they are a UIButton using isMemberOfClass to get your button. If you have multiple buttons you could then check the text of the button or some other property that uniquely identifies it. That would be a hacky way to do it.
You have to make a custom cell like that:
CustomCell.h
#protocol CustomCellDelegate <NSObject>
- (void)buttonPressed:(UIButton *)sender;
#end
#import <UIKit/UIKit.h>
#interface CustomCell : UITableViewCell
#property (weak, nonatomic) id<CustomCellDelegate> delegate;
#property (weak, nonatomic) IBOutlet UIButton *button;
- (IBAction)buttonPressed:(UIButton *)sender;
#end
CustomCell.m
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)prepareForReuse{
self.button.enable = YES;
}
- (IBAction)buttonPressed:(UIButton *)sender{
[self.delegate buttonPressed:sender];
}
#end
after in IB you add a new UITableViewCell at your UITableView and the class of it with you new custom cell set the Identify ID like "CustomCell" add your Button to your custom cell and connect the Outlet, then you modify you tableView:cellForRowAtIndexPath: like that:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier=#"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.delegate = self;
return cell;
}
- (void)buttonPressed:(UIButton *)sender{
sender.enable = NO;
}
Also you have to add the CustomCellDelegate in your controller's heater file
One simple way would be to keep a NSMutableArray variable in your viewcontroller and with that keep track of what cells button is disabled/enabled. And use the UITableViewDataDelegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to set the buttons state each time it gets displayed. And UITableViewDelegate method:
– tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)tableViewCell forRowAtIndexPath:(NSIndexPath *)indexPath
to write to the array. Indexing with indexPath.

Recipes to pass data from UITableViewCell to UITableViewController

I'm figuring out the right mechanism to pass data from UITableViewCells to a UIableViewController (or UIViewController).
Searching within stackoverflow I found different ways to do this and finally I found a mechanism that works well but I don't know if it could be a valid approach.
This is the scenario. First, I created a custom cell (associated with a xib interface), named DataTableViewCell, that extends UITableViewCell. This cell has some outlet to display (and modify) data and an addictional property called index like the following:
#property (nonatomic, copy) NSIndexPath* index;
This property is refreshed inside the method cellForRowAtIndexPath method within the UITableViewController:
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DataTableViewCell *cell = (DataTableViewCell*)[tv dequeueReusableCellWithIdentifier:kCellTableIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"DataTableViewCell" owner:self options:nil];
cell = (DataTableViewCell*)self.dataTableViewCellOutlet;
}
// configure the cell with data
// do stuff here...
// configure the cell with the current indexPath
cell.index = indexPath;
return cell;
}
Since it is possible to change values within a cell, I had the need to pass data to the UITableViewController for updating the model. To do that I decided to use a delegate mechanism. So, I created a protocol with a method like the following:
- (void)updateData:(DataItem*)dataItem atIndexPath:(NSIndexPath*)index;
The UITableViewController implements that protocol. In this way, within the cell (against to some events), I can call that method and update the model in the correct way.
Having said this, I have some questions to ask:
Is this a right mechanism to pass data from a cell to a controller?
Is it correct to using an index property like the one use in the cell?
Is it the same using retain policy instead of copy policy. What could be the difference?
Since the solution I found could be very scheming, is it possible to use block insteads?
About blocks, I thought this way:
Within the cell create a property block like the following:
#property (nonatomic, copy) void (^updateModelOnEvent)(DataItem* dataItem);
Then inside the method cellForRowAtIndexPath method within the UITableViewController assign that property to a block code like this (this code is at the same level of cell.index = indexPath;):
// configure the cell with the current indexPath
cell.updateModelOnEvent = ^(DataItem* dataItem) {
[self.model insertObject:dataItem atIndex:indexPath.row];
};
Could be a valid alternative? In this case, do I have to use copy or retain policy?
Thank you in advance.
Why not just use [UITableView indexPathForCell:] with a delegate?
MyViewController.h
#interface MyViewController : UITableViewController <MyTableViewCellDelegate>
#end
MyViewController.m
#implementation MyViewController
// methods...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *reuseIdentifier = #"MyCell";
MyTableViewCell *cell = (id)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell == nil)
cell = [[[MyTableViewCell alloc] initWithMyArgument:someArgument reuseIdentifier:reuseIdentifier] autorelease];
[cell setDelegate:self];
// update the cell with your data
return cell;
}
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell {
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// update model
}
#end
MyTableViewCell.h
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (assign, nonatomic) id <MyTableViewCellDelegate> delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier;
#end
#protocol MyTableViewCellDelegate <NSObject>
#optional
- (void)myDelegateMethodWithCell:(MyTableViewCell *)cell;
#end
MyTableViewCell.m
#implementation MyTableViewCell
#synthesize delegate = _delegate;
- (id)initWithMyArgument:(id)someArgument reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self) {
// custom setup
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
self.delegate = nil;
}
- (void)someActionHappened {
if ([self.delegate respondsToSelector:#selector(myDelegateMethodWithCell:)])
[self.delegate myDelegateMethodWithCell:self];
}
#end
To modify cells you should modify data model and reload table data. Nothing else.
Not necessary to have a indexPath for cell
In your case it is the same using retain or copy policy. Copy makes new objects with same state.

Resources