Reputation: 55
I've searched high and low to no avail on this odd issue. I have a custom subclassed UITableViewCell. Two views for a front and back, and when tapped the cell flips over. This is done like so:
- (IBAction)cellFrontTapped:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (_cellFlag == 0)
{
_cellFlag = 1;
[UIView transitionFromView:_cellFrontView toView:_cellBackView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[self performSelector:@selector(cellBackTapped:) withObject:self afterDelay:15.0];
}
}
- (IBAction)cellBackTapped:(id)sender {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (_cellFlag == 1)
{
_cellFlag = 0;
[UIView transitionFromView:_cellBackView toView:_cellFrontView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
}
}
This works fine with just a few cells. Up to a few pages worth. But when I load a larger data set, tapping on one cell flips it over like expected, but also flips over other cells.
I realize this is because the cells are being re-used by dequeueReusableCellWithIdentifier
but I cannot decipher how to prevent it.
I've tried implementing it as one UIGestureRecognizer
. I've tried utilizing didSelectRowAtIndexPath
like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = (MyTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
if (cell.cellFlag == 0)
{
[cell cellFrontTapped];
}
else
{
[cell cellBackTapped];
}
}
These all work to flip the cell properly, but the tap event always flips other cells in the list. The only solution I've found is to not use dequeueReusableCellWithIdentifier
like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyTableViewCell"];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"MyTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Here's my current subclass implementation:
@implementation MyTableViewCell
- (void)awakeFromNib {
// Adding the recognizer here produced the same result
//UIGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellTapped)];
//tap.delegate = self;
//[self addGestureRecognizer:tap];
_cellFlag = 0;
}
- (void)cellFrontTapped {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
_cellFlag = 1;
[UIView transitionFromView:_cellFrontView toView:_cellBackView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
[self performSelector:@selector(cellBackTapped) withObject:self afterDelay:15.0];
}
- (void)cellBackTapped {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
_cellFlag = 0;
[UIView transitionFromView:_cellBackView toView:_cellFrontView
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionShowHideTransitionViews
completion:NULL];
}
-(void)cellTapped
{
if (_cellFlag == 0)
{
[self cellFrontTapped];
}
else
{
[self cellBackTapped];
}
}
@end
Here's the `configureCell:atIndexpath:
- (void)configureCell:(MyTableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath
{
NSManagedObject *t = [[self fetchedResultsController] objectAtIndexPath:indexPath];
//here i set the labels and such
cell.cellBackAmountLabel.text = [t.amount formattedAmount];
//added
if ([self.flippedCellIndexes containsObject:indexPath])
{
[cell.contentView bringSubviewToFront:cell.cellBackView];
}
else
{
[cell.contentView bringSubviewToFront:cell.cellFrontView];
}
}
I'm at a loss. Not using dequeueReusableCellWithIdentifier
makes a significant performance impact in my case. Help!
Upvotes: 1
Views: 327
Reputation: 6068
I would recommend separating your model from your view. What this means is that you should keep track of which cells should render as flipped, but not on the cell itself. Lets say you declare an NSArray
(probably NSMutableArray
, even) called flippedIndexes
in your view controller.
If your data set is as big as you say, then probably you should use a sparse array instead of NSArray
. Sparse arrays can easily be implemented with NSMutableDictionary
. NSMutableSet
could also work.
When dequeueing the cell, you would then check which cells should be flipped, probably at that configureCell:atIndexPath:
method of yours. Basically, if the indexPath
shows on your sparse array or set you render the cell as flipped. This would imply dropping the cellFlag
property you have declared on your cell and toggling its state according to the model I've been mentioning. For flipping a cell, check the flippedIndexes
property for the given indexPath
and act accordingly. Something like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = (MyTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
if (![self.flippedIndexes containsObject:indexPath])
{
[cell cellFrontTapped];
[self.flippedIndexes addObject:indexPath];
}
else
{
[cell cellBackTapped];
[self.flippedIndexes removeObject:indexPath];
}
}
Here I'm assuming the use of NSMutableSet
for the flippedIndexes
property. Notice that this will only work properly if you also check the model in configureCell:atIndexPath:
, as otherwise you'll have cells magically clearing when scrolling.
Moral of the story is: don't store state information in queued cells.
Upvotes: 2