Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionCell to call method in CollectionViewController

I have a standard view controller that contains a collection view.

I am populating the collection view with custom cells created from a custom subclass of UICollectionViewCell.

  1. What is the best practice of calling a method in the collection view class from within the collection view cell class

  2. Why doesnt the following init get called in my collection view cell class, I thought this would be called when the collectionView class creates my cells.

    (id)initWithFrame:(CGRect)frame

The reason I want to do this is because I am deleting a collection view cell, the delete function is inside the collection view cell class. I want to play a sound when it deletes, and would rather init the sound/s in the collection view controller class, and not for each cell.

like image 507
DogCoffee Avatar asked Dec 03 '22 21:12

DogCoffee


2 Answers

You really should not add a back-reference from the cell to the CollectionView as an iVar or a property, because you'll end up with a really (really) bad cross-reference (objA points to objB and objB points to objA). Besides minor issues like compile errors (can be fixed badly with @class statements), this also results in severe issues like non-deallocatable objects, memory leaks, zombies and so on.

The rule of thumb is:

parents know about their children, but children must not know about their parents.

In other words: The CollectionView knows its cells, but the cells should not know their CollectionView.

Possible solutions

  1. redesign your task and your solution. Maybe add a tap gesture recognizer on the collectionView and not on the cell. There is -indexPathForItemAtPoint: to give you the selected cell.

  2. If you absolutely need a back-reference: define a protocol and use a delegate. This is a common design pattern in Cocoa / Cocoa Touch. You should read about the delegate design pattern, but in short, this is how you do it:


define the protocol in your cell (Remember, the cell doesn't know about the type of the parent, by defining this protocol, it just knows for sure there are one or more methods available on the "parent")

// in cell.h
@protocol MyCellProtocol
- (IBAction)doSomething:(id)sender;
@end

add a delegate of type id (this means, it can be any object, as long as it conforms to the protocol. In other words: this will be your collectionView, but you don't need to have a reference to it!

// in cell.h
@property (assign) id<MyCellProtocol> cellDelegate;

now you can invoke the delegate in your cell:

// in cell.m, some method:
[self.cellDelegate doSomething:nil];

finally you need to set the delegate. When you create/setup your cell in the UICollectionViewController, set your controller as the delegate:

// in collectionViewController.m
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = 
      [self.collectionView dequeueReusableCellWithReuseIdentifier:@"myCell" 
                                                     forIndexPath:indexPath];
    cell.cellDelegate = (id<MyCellProtocol>)self;
like image 89
auco Avatar answered Jan 08 '23 19:01

auco


By default, UICollectionViewCell has no reference to the UICollectionView that contains it. So if you want to communicate from the cell to the collection view, you need to add a property or ivar in the cell.

cell = [UICollectionViewCell ...];
cell.collectionView = self.collectionView;

Second, when instantiating a UICollectionViewCell from a nib, initWithFrame: is not called; initWithCoder: is called instead. You can override initWithCoder: (and call super), or implement -awakeFromNib.

Sometimes what I do if I have to implement two init methods in one class (such as initWithFrame: and initWithCoder: ) is, I have each implementation call a single method called commonInit

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    } 
    return self;
}

- (id)initWithCoder:(NSCoder *)encoder
{
    self = [super initWithCoder:encoder];
    if (self) {
        [self commonInit];
    } 
    return self;
}

- (void)commonInit
{
// set up your instance
}

This eliminates code duplication and provides consistent behavior.

Reference: UIView documentation for initWithFrame:

like image 21
bneely Avatar answered Jan 08 '23 19:01

bneely