dequeueReusableCellWithReuseIdentifier doesn't go through init nor initWithCoder functions - ios

When creating cells for UICollectionView, dequeueReusableCellWithReuseIdentifier doesn't go through init nor initWithCoder function of CategoryView.
The view is creating, it has a proper type (CategoryView) but init nor initWithCoder of CategoryView is not called, so essential functionality is not executed. Is there some other init in this senario?
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CategoryView *cell = [cv dequeueReusableCellWithReuseIdentifier:#"CategoryView" forIndexPath:indexPath];
[cell someConfiguration];
return cell;
}

In this case, the problem was that the base class was not specified for your cell prototype in Interface Builder. So make sure the base class is set:
Then, when you call dequeueReusableCellWithReuseIdentifier, using the storyboard identifier you specified in the prototype cell, it calls initWithCoder when the cell is first instantiated. If the cell scrolls out of view and is later re-used for another NSIndexPath, the prepareForReuse is called:
#interface CategoryView : UICollectionViewCell
#end
#implementation CategoryView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
NSLog(#"init");
}
return self;
}
- (void)prepareForReuse {
[super prepareForReuse];
NSLog(#"reuse");
}
#end

try - (instancetype)initWithFrame:(CGRect)frame
It works for me. I wrote UI programmatically.
Good luck!

UICollectionViews and UITableViews reuse cells to improve performance. initWithCoder: will only run once per reusable cell. As such, if you need something called every time a cell is displayed I would recommend writing a method as follows in your cellForRowAtIndexPath: method:
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
CategoryView *cell = [cv dequeueReusableCellWithReuseIdentifier:#"CategoryView" forIndexPath:indexPath];
// self.parameters = an NSDictionary of the colors, text, etc. you need to the cell to know about
[cell configureWithParameters:self.parameters];
return cell;
}
Then, inside your configureWithParameters: method you can include colors, text, etc. that will help you setup your CategoryView.
You'll have to declare your configureWithParameters: method in your CateogryView.h as follows:
// CategoryView.h
- (void)configureCell:(NSDictionary *)parameters;
Then include your customizations in the .m as follows:
// CategoryView.m
- (void)configureCell:(NSDictionary *)parameters{
// Put Whatever initialization code you need here
// Example:
self.label.textColor = parameters["color"];
self.label.text = parameters["text"];
}

Make sure you have collection view delegate/datasource connected to your class.
Make sure you give base class for cell.
Make sure your cell has reuse identifier.
Make sure to register Nib for your reuse identifier. (You don't need to do that if your cell present in your collection view in storyboard).
And finally try changing
- (CategoryView *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
to
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Now your
- (instancetype)initWithCoder:(NSCoder *)aDecoder
Should be called.

Assuming you've subclassed UICollectionViewCell, you can put your initialization code in awakeFromNib().

Related

Init method for custom UICollectionViewCell doesn't fired

I have UICollectionView that use custom UICollectionViewCell class called ClaimInfoCell. I want to fire its init method by overriding initWithCoder: but this method doesn't fired.
- (id)initWithCoder:(NSCoder*)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
// Do something
}
return self;
}
I checked in collectionView:cellForItemAtIndexPath: and everything is fine. NSLog-ing that cell, and it was the ClaimInfoCell's instance.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ClaimInfoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"claimInfoCell" forIndexPath:indexPath];
return cell;
}
Already followed this and this answer, but I can't find the right that fits on mine.
I use storyboard and customize that cell on its collection view directly. So how I should declare my custom init method my cell? Any help would be appreciated. Thank you.

Access the UICollectionViewCell Components in custom method

I have some components in UICollectionViewCell and i can access all inside the cellForItemAtIndexPath. I need to access the component in a custom method,
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
// UIButton
cell.btn1.frame =CGRectMake(380,100, 150, 40);
[cell.btn1 addTarget:self action:#selector(btnClicked) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
In custom method,
-(void)btnClicked{
MycollectionclassView *cell =[[MycollectionclassView alloc]init];
cell.btn1.backgroundColor =[UIColor greenColor];
}
Here I cannot change the color of the button.
You must create a protocol and create a IBAction to a method. When your action method is called, you can notify the protocol and its screen that has the UICollectionView protocol will receive this event.
On your cell header file.h define the protocol:
#protocol YourCellNamelDelegate <NSObject>
#required
/**
* Notifies that current ITEM was clicked on icon.
*
* #param cell The current cell that was clicked.
*/
-(void)bookmarkClicked:(YourCellName *)cell; // OR without argument
#interface YourCellName : UITableViewCell
/**
* Reference to 'YourCellNameDelegate' delegate.
*/
#property (assign,nonatomic) id<YourCellNamelDelegate>delegate;
...
On your cell file.m
...
- (IBAction)bookmarkClickAction:(id)sender {
// change or update screen element here ...
if (self.delegate) {
[self.delegate bookmarkClicked:self];
}
}
On your ViewController implement the your protocol
#interface SMGListViewController () <YourCellNameDelegate>
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// get your cell here, for example in my case:
SMGCityGuideCollectionViewCell *cell;
cell = (SMGCityGuideCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
SMGTouristInfo *touristInfo = [self.listTouristInfo objectAtIndex:indexPath.row];
[cell setTouristInfoCell:touristInfo];
// IMPORTANT HERE *************************
cell.delegate = self;
// IMPORTANT HERE *************************
return cell;
}
...
#pragma mark <SMGPlaceCellDelegate>
-(void)bookmarkClicked:(SMGPlaceCell *)cell {
// notify events here ...
NSIndexPath *index = [self.tableViewPlaces indexPathForCell:cell];
[self.tableViewPlaces reloadRowsAtIndexPaths:#[index] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I hope it helps! :)
To make things clear , when cellForItemAtIndexPath gets called it is the time that your cells get laid in the view , in your custom method you are just creating a cell object but this object does not reference the objects that is laid out in your collection view , so your just changing the color of a newly created object that does not reference anything .
In the Button Click function pass arguments as Sender , by which in the function call you can access the collectionView cell as Sender.SuperView .
You can also find the indexpath from the collectionviewcell now.
Now if you want to update a UI of the Collection view cell , you will have to do it in the cellforItemAtIndexPath function. which can be called by [collectionview reloadData].
So I would suggest to keep some flag variable in the data source to check if you have to update the UI or not.

Where are the UITableViewCell's initialized?

I've created my own CustomTableView and CustomCell. The cell is in a xib, and I'm registering it when initializing the tableView, like this:
[self registerNib:[UINib nibWithNibName:#"CustomCell" bundle:nil]
forCellReuseIdentifier:kCustomCellIdentifier];
If I don't do this, I won't be able to define what ReuseIdentifier should "point" to this class. This is my cellForRow:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [self dequeueReusableCellWithIdentifier:
kCustomCellIdentifier forIndexPath:indexPath];
if(!cell)
{
cell = [[[NSBundle mainBundle] loadNibNamed:#"CustomCell"
owner:self options:nil] objectAtIndex:0];
cell.delegate = self;
[cell initialSetup]; //Other stuff
}
else
[cell resetCell];
//And other awesome stuff
return cell;
}
This 'works'. When I lauch my app, my own custom cells are showing.
However, it turns out the cell is NEVER returned as nil from [self dequeue... Thus, the if-statement if(!cell) is never true. I have additional setup inside this statement that I want to perform, but I don't know where the cell's are being initialized the first time now. If I remove registerNib, then this statement is true, but then it's true for all cells, and none will ever be dequeued.
I can probably work around this, and put my initialSetup (and other stuff) inside the -(id)initWithCoder..-method in the CustomCell-class, but I'd like to know where my cells are being initialized right now. Why do my cells exist before cellForRowAtIndexPath?
When you register a class or a nib in a table view using method registerClass:forCellReuseIdentifieror registerNib:forCellReuseIdentifier the tableview internally will create an instance of the cell if no one is available when you call dequeueReusableCellWithIdentifier:, so initialization code is no longer needed inside the delegate.
From the UITableView.h code:
// Beginning in iOS 6, clients can register a nib or class for each cell.
// If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned.
// Instances returned from the new dequeue method will also be properly sized when they are returned.
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);
Depending of the which register method is used the init methods called are:
initWithStyle:reuseIdentifier for cells registered using registerClass:
initWithCoder: for cells registered using registerNib:
If you are using registerNib: you can use too awakeFromNib method in the cell, that is also a good place to put initialization code of the cell. The main difference between using initWithCoder: or awakeFromNib its explained in this question.
When a cell is reused, you have the method prepareForReuse in the cell to make some cleanup in the cell and left it prepared to be configured again.
A good approach to work with all of this will be:
//ViewController code
- (void)viewDidLoad
{
...
[_tableView registerNib:[UINib nibWithNibName:#"CellSample" bundle:Nil] forCellReuseIdentifier:#"cellIdentifier"];
...
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
//configure the new cell, no if (!cell) needed
return cell;
}
//Cell code
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
//You can put initialization here
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
//But is better initialize here
}
- (void)prepareForReuse
{
//Reuse and reset the cell here
}
Hope it helps
When using dequeueReusableCellWithIdentifier: forIndexPath: you don't have to alloc the cell like you would if you were using default UITableViewCell. In your custom UITableViewCell subclass, this is called when it is initialized:
- (void)awakeFromNib {
[super awakeFromNib];
}
So add that in there and you should be good.
- (instancetype)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
//custom your cell
}
return self;
}

Customizing uitableviewcell when using storyboards

I'm doing my project with storyboards and I'm trying to implement a custom UITableViewCell.
I would like to do the following in my custom cell:
#import "CustomCell.h"
#implementation CustomCell
#synthesize myLabel, myButton;
- (instancetype)initWithCoder:(NSCoder *)decoder
{
if ((self = [super initWithCoder:decoder]))
{
//want to custom setup of properties placed in the cell
self.myButton.backgroundColor = [UIColor redColor];//this does NOT work
//and so forth...
}
return self;
}
But the background color is not set. It only works when I set the background color of the button in the tableViewController's cellForRowAtIndexPath function, like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
MyObject *obj = self.myObjects[indexPath.row];
cell.myButton.backgroundColor = [UIColor redColor];//This works!
cell.myLabel.text = obj.name;
return cell;
}
And I have trying debugging by setting break points and NSLog and the initWithCoder gets called before cellForRowAtIndexPath for every cell??
But the background color of the button in the cell does not show when I set it in the custom cell.
Can any help?
Try using the awakeFromNib method instead of initWithCoder: to do any initial customization of the cell. And as mentioned in comments, for simple things like background colors of controls, you can probably just do that in Xcode via the storyboard.

Why am I getting a deallocated memory call when I call UICollectionViewCell dequeueReusableCellWithReuseIdentifier?

I have a UICollectionView that contains custom UICollectionViewCells (TestReceiptCell is the class name).
I was not having any problems getting the UICollectionView to appear and load the custom cells when the custom cells only contained a UILabel.
I then added a UITableView via IB into the TestReceiptCell NIB file. I set a referencing outlet in TestReceiptCell.h for the UITableView and synthesized in the .m file. I set the delegate and datasource for the UITableView to the ViewController containing the UICollectionView.
Now when running the app I get a EXC_ BAD_ ACCESS exception in this block on the third line:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"TestReceiptCell";
TestReceiptCell *cell = (TestReceiptCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; //exception thrown here
return cell;
}
I ran the Zombie Instrument test and found that the deallocated memory call originates here. This is my first time using that instrument so I am not exactly sure how to investigate from here.
For reference, here are some more relevant parts of the code:
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.myCollectionView registerNib:[UINib nibWithNibName:#"TestReceiptCell" bundle:nil] forCellWithReuseIdentifier:#"TestReceiptCell"];
// Setup flowlayout
myCollectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[myCollectionViewFlowLayout setItemSize:CGSizeMake(310, 410)];
[myCollectionViewFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.myCollectionView setCollectionViewLayout:myCollectionViewFlowLayout];
self.myCollectionView.pagingEnabled = YES;
}
I am implementing the UITableView datasource and delegate methods in the ViewController.m file as well but I am not sure if the problem lies here given the origination of the EXC_BAD_ACCESS exception:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:#"eventCell"];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"eventCell"];
}
return cell;
}
UPDATE:
I am able to get this to run if I change cellForItemAtIndexPath to:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"TestReceiptCell";
//TestReceiptCell *cell = (TestReceiptCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
TestReceiptCell *cell = [NSBundle.mainBundle loadNibNamed:#"TestReceiptCell" owner:self options:nil][0];
return cell;
}
However, I am not dequeuing cells and know this is not the correct way. There seems to be an issue somewhere in the initWithFrame method that gets called when dequeueReusableCellWithResueIdentifier creates a new cell. Here is that method currently:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:#"TestReceiptCell" owner:self options:nil];
if ([arrayOfViews count] < 1) {
return nil;
}
if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[UICollectionViewCell class]]) {
return nil;
}
self = [arrayOfViews objectAtIndex:0];
}
return self;
}
EDIT:
If I do not select a delegate or a datasource for the tableview, the collectionview with tableviews will load. Something in attaching the delegate/datasource to File's Owner is causing the error.
When you register a UINib for cell reuse, dequeueReusableCellWithReuseIdentifier:forIndexPath: is what calls instantiateWithOwner:options: on the UINib that you registered. Whatever it passes for owner, is what becomes the File's Owner outlet in your nib.
It appears that you are expecting the File's Owner to be the UICollectionView, but I don't think that it is.
Even if it were, I don't think you should use the UICollectionView for the delegate of the UITableView contained within each collection cell. That would require your UICollectionView to keep track of the tableViews and contents within each cell.
I'd suggest setting the delegate of the contained tableView to the collection cell itself and have each cell manage its own tableview.
EDIT:
You can define a delegate protocol for your collection view cells to communicate the relevant table view events to the collection view. With this approach, you would set the delegate property you define for each collection cell in the collectionView:cellForItemAtIndexPath method of your collection view datasource.
When the user, for example, selects an item from the table, you can call the cell delegate to inform the collection view which item was selected.
This approach allows you to abstract the fact that your collection cell is using a table view to display the cell information. Later, if you decide you want to use, for example, an embedded UICollectionView to display those items, the delegate protocol can remain unchanged and you can isolate your changes to the collection cell.

Resources