Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with Decorator pattern, iOS / UICollectionViewCells

I am trying to 'decorate' UICollectionViewCells using the Decorator pattern.

For example, if I have a

 BaseCell : UICollectionViewCell 

I would like to be able to do something like this:

 BaseCell *cell = [[BaseCell alloc] initWithFrame]
 cell = [[GlowingCell alloc] initWithCell:cell];
 cell = [[BorderedCell alloc] initWithCell:cell];
 cell = [[LabelledCell alloc] initWithCell:cell];

 // cell is now a glowing, bordered, labelled cell.

I think the Decorator pattern is pretty neat for this sort of thing, but I'm having difficulty applying it to collection views.

Firstly, in UICollectionViewControllers you need to register a class, like this:

 [self.collectionView registerClass:cellClass forCellWithReuseIdentifier:cellIdentifier];

So I don't have an opportunity to make my own instance.

Secondly, I can't see how the Decorator can ever be useful for decorating 'non-pure' objects, that is, objects that I didn't create from the ground up but have their own properties and behaviours (such as UICollectionViewCell). Since in the example above, cell represents a new instance of LabelledCell, and if UICollectionView makes a call on a method, eg, isSelected, this will call aLabelledCellInstance.isSelected unless I specifically do this in my Decorator base class:

 - (BOOL)isSelected {
      return self.decoratedCell.isSelected;
 }

Which is fine for one method but it doesn't seem right to have to override every method in UICollectionViewCell. Should I be using forwardInvocation:?

Am I abusing this pattern, and are there alternatives? Because it works very nicely in books when you just have to override basic methods like

 getPrice() {
      return decoratedObject.getPrice() + 1.10f;
 }

.. but seems hard to fit to the purpose of actually decorating existing UI elements with custom behaviour.

Thanks

EDIT: What I am trying to avoid is having classes like this:

  • GlowingBorderedCell
  • LabelledGlowingBorderedCell
  • etc.

On paper the Decorator is the perfect candidate for what I am trying to achieve but the implementation is absolutely stumping me.

like image 460
Sam Avatar asked Feb 01 '13 18:02

Sam


2 Answers

Firstly, the Decorator pattern requires you to override all the base methods in the BaseDecorator so you can forward the calls to the decorated object. And you can do this either by overriding every single method or, preferably, just use forwardInvocation:. And since all other decorators will be subclasses of BaseDecorator, you can now just override the methods you want to change.

Secondly, for the CollectionView issue, I suggest to use the Decorator pattern with normal UIViews, and then use the decorated view as the contentView of the cell. Let's see an example:

We have BaseCellView class which will be the super class for all decorators.

BaseCellView : UIView;
GlowingCellView: BaseCellView;
BorderedCell: BaseCellView;
LabelledCell: BaseCellView;

And we still have our BaseCell class which is a subclass of UICollectionViewCell:

BaseCell : UICollectionViewCell;

Now, UICollectionViewControllers will always create an instance of BaseCell and give you the chance to configure it, where you'll do the following:

BaseCellView *cellView = [[BaseCellView alloc] initWithFrame]
cellView = [[GlowingCellView alloc] initWithCellView:cellView];
cellView = [[BorderedCellView alloc] initWithCellView:cellView];
cellView = [[LabelledCellView alloc] initWithCellView:cellView];
cell.contentView = cellView;

And you still can forward any UICollectionViewCell to the decorator if you want that.

like image 119
Hejazi Avatar answered Dec 09 '22 02:12

Hejazi


Here's a post in which I've described this technique. Although I've chosen to decorate the UITableView and not the cell, it could be easily adapted to your collection view. It's a pretty long read so I will only make a short summary here:

  • the decorator needs to be a proxy object to be able to forward all the messages to the decorated object
  • there are multiple methods you need to override in your decorator to make that happen, among which respondsToSelector and forwardingTargetForSelector
  • when done, you'll be able to chain the decorators which is pretty neat:

e.g.:

dec = [[DEFooterDecorator alloc] initWithDecoratedObject:dec];
dec = [[DEHeaderDecorator alloc] initWithDecoratedObject:dec];
dec = [[DEGreenCellDecorator alloc] initWithDecoratedObject:dec];
like image 29
Rad'Val Avatar answered Dec 09 '22 01:12

Rad'Val