shtefane
shtefane

Reputation: 454

UICollectionView interactiveTransitionToCollectionViewLayout crashes with exception

I have a collectionViewController with horizontal collectionView like in Paper App. I added pan gesture to change from one layout to other and I used interactive transition. It works well if you drag and wait when animation is finished, but if you drag faster several times and don't wait animation to be finished or canceled app throw an exception:

Assertion failure in -[UICollectionView _finishInteractiveTransitionShouldFinish:finalAnimation:], /SourceCache/UIKit_Sim/UIKit-2935.137/UICollectionView.m:2691 
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection was not prepared for an interactive transition. see startInteractiveTransitionToCollectionViewLayout:completion:'

Gesture handler code :

- (void)oneFingerGesture:(UIPanGestureRecognizer *)sender
 {

if (sender.state == UIGestureRecognizerStateEnded ||
    sender.state == UIGestureRecognizerStateCancelled)
{
    if (self.transitionLayout.transitionProgress > 0.2) {
        [self.collectionView finishInteractiveTransition];
    } else {
        [self.collectionView cancelInteractiveTransition];
    }
}else
{
    CGPoint point = [sender locationInView:sender.view];

    if (sender.state == UIGestureRecognizerStateBegan && !self.transitionLayout && !_isInTransition)
    {
        invertPan = self.largeLayout == self.collectionView.collectionViewLayout;

        UICollectionViewLayout *toLayout = invertPan ? self.smallLayout : self.largeLayout;

        self.transitionLayout = [self.collectionView startInteractiveTransitionToCollectionViewLayout:toLayout
                                                 completion:^(BOOL completed, BOOL finish) {
                                                     self.transitionLayout = nil;
                            _isInTransition = NO; }];


        self.initialTapPoint = point;
        _isInTransition = YES;

    }else if(sender.state == UIGestureRecognizerStateChanged && self.transitionLayout && _isInTransition)
    {
        CGFloat distance = _initialTapPoint.y - point.y;
        if (invertPan) {
            distance = -distance;
        }
        CGFloat dimension = self.collectionView.bounds.size.height - 200;
        CGFloat progress = MAX(MIN(((distance)/ dimension), 1.0), 0.0);
       [self.transitionLayout setTransitionProgress:progress];

    }
}
 }

Upvotes: 0

Views: 1307

Answers (1)

Thor Frølich
Thor Frølich

Reputation: 664

The documentation states that calls to finishInteractiveTransition: and cancelInteractiveTransition: will install the layout object the transition is going to and from respectively. However, this appears to not happen immediately. Thus, if the gesture triggering and driving the transition can happen in quick succession, it is not sufficient to check whether the current layout is a UICollectionViewTransitionLayout or a subclass thereof before calling one of the two methods to end the transition.

I solved the problem by introducing a BOOL ivar (_isFinishingOrCancellingTransition) to avoid ending a transition that is already in the process of ending:

if (_isFinishingOrCancellingTransition) return;

if (!self.transitionLayout) return;
if (self.collectionView.collectionViewLayout != self.transitionLayout) return;

_isFinishingOrCancellingTransition = YES;

if (self.transitionLayout.transitionProgress > 0.5)
{
    [self.collectionView finishInteractiveTransition];
}
else
{
    [self.collectionView cancelInteractiveTransition];
}

and then resetting the BOOL when the transition is completed:

self.transitionLayout = [self.collectionView startInteractiveTransitionToCollectionViewLayout:toLayout completion:^(BOOL completed, BOOL finish) {
    self.transitionLayout = nil;
    _isFinishingOrCancellingTransition = NO;
}];

Upvotes: 1

Related Questions