简体   繁体   中英

Weird behavior UIView animation on gesture recognizer's sender.view in paging UIScrollView

Setup:

I have a horizontal paging UIScrollView set up with help of the LXPagingViews class (to easily add a 'peeping' UIScrollView ). This is set up, so you can flick between different views in the UIScrollView . I have added a UIPinchGestureRecognizer to every view, so when the user pinches on a view, it displays a 'zooming' animation, and segues into a detail view. A screenshot for more clarity:

我的页面UIScrollView

Problem:

When I pinch one of the views, the zoom animation starts and the view segues into the detail view. This works fine, EXCEPT for the last view in the UIScrollView . In the last view (doesn't matter how many views it has, as long as it has more than 2), when the animation plays, it spawns a new view behind the view I tapped, and animates that one. This makes for a weird animation. Also, the view doesn't get blocked from input by the animation, because it spawns a new one. This means that the user can spawn multiple views quickly by pinching fast.

I hope this screenshot illustrates the problem a bit:

当用户捏捏时,会在后面生成新视图并进行动画处理

What I have tried:

  • Debugging the gesture recognizer's superview subviews, to see if more views spawn
  • Monitoring what view get's animated. For every pinch the user does, another view gets animated for some reason
  • Tried to use another way of setting the image instead of lazy-loading. Didn't help
  • Tried other images, more and less views, orientation differences and empty views.

Code:

Adding the gesture recognizer to the view:

- (UIView<ReusableView> *)pagingView:(PagingView *)thePagingView reusableViewForPageIndex:(NSUInteger)thePageIndex withFrame:(CGRect)theFrame {
    // View's Identifier
    static NSString *theIdentifier = @"voucherDetailView";
    VoucherDetailView *thePageView = (VoucherDetailView *)[thePagingView dequeueReusableViewWithIdentifier:theIdentifier];
    if (thePageView == nil) {
        thePageView = [[VoucherDetailView alloc] initWithFrame:theFrame];
    } else {
        thePageView.frame = theFrame;
    }

    Voucher *voucher = [_vouchers objectAtIndex:thePageIndex];
    NSURL *fullVoucherImageUrl = [NSURL URLWithString:voucher.fullVoucherImage];
    [thePageView.voucher setImageWithURL:fullVoucherImageUrl placeholderImage:[UIImage imageNamed:@"11283253-nederland_kaart"]];

    // Gesture recognizer    
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(openZoomablePinch:)];
    [thePageView addGestureRecognizer:pinchGesture];

    return thePageView;
}

The pinch handler:

- (void)openZoomablePinch:(UIPinchGestureRecognizer*)sender {
    // Only allow one pinch to activate the animation
    if (sender.state == UIGestureRecognizerStateBegan) {
        // Bring the view to the front so it doesn't clip with the peeping views
        [sender.view.superview bringSubviewToFront:sender.view];
        CGAffineTransform trans = sender.view.transform;
        [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseOut
                         animations:^{
                             sender.view.transform = CGAffineTransformScale(sender.view.transform, 1.15, 1.15);
                         }
                         completion:^(BOOL finished) {
                             if (finished && self.view.window) {
                                 sender.view.transform = trans;
                                 [self performSegueWithIdentifier:@"voucherZoomSegue" sender:self];
                             }
                         }];
    }
}

EDIT: PagingView code that gets called on the last view when pinching:

    UIView<ReusableView> *theRightMostReusableView = [self.visibleReusableViews lastObject];
    NSUInteger theRightMostPageIndex = (NSUInteger)floorf(CGRectGetMinX(theRightMostReusableView.frame) / thePageWidth);
    while ((theRightMostPageIndex != MAX(0, self.numberOfItems - 1)) && (theRightMostPageIndex < theToIndex)) {
        theRightMostPageIndex = MIN(theRightMostPageIndex + 1, MAX(0, self.numberOfItems - 1));
        CGFloat theMinX = theRightMostPageIndex * thePageWidth;
        CGRect theRect = CGRectMake(theMinX, 0.0f, thePageWidth, thePageHeight);
        UIView<ReusableView> *theReusableView = [self.dataSource pagingView:self reusableViewForPageIndex:theRightMostPageIndex withFrame:theRect];
        if (!CGRectContainsRect(theRect, theReusableView.frame)) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                           reason:[NSString stringWithFormat:
                                                   @"theReusableView's frame (%@) must be contained by the given frame (%@)",
                                                   NSStringFromCGRect(theReusableView.frame),
                                                   NSStringFromCGRect(theRect)]
                                         userInfo:nil];
        }
        [self.visibleReusableViews addObject:theReusableView];
        [self addSubview:theReusableView];
    }

EDIT 2:

I have uploaded a small demo project here . The way to use it, set the simulator (or your device) in landscape, go to Peeping Paging View, and try to pinch/tap the views (except the first one). As you can see, the views get zoomed, except for the last view, which spawns a new view behind it and zooms that one (the '9' stays the same size).

The code is in the PeepingPagingViewController.m .

Solution (thanks to Oladya Kane):

The rightmost visible index is mistakingly indexed as the leftmost visible index. The code I had to change was the line:

NSUInteger theRightMostPageIndex = (NSUInteger)floorf(CGRectGetMinX(theRightMostReusableView.frame) / thePageWidth);

to

NSUInteger theRightMostPageIndex = MIN((NSInteger)floorf((CGRectGetMaxX(theRightMostReusableView.frame) - 0.1f) / thePageWidth), MAX(0, self.numberOfItems - 1));

I've looked through you demo code and found following bug:

When your pinch gesture recognizer is called, you animate it's sender frame. This forces PageView to layout its subviews -> it calls your delegate method

- (UIView<ReusableView> *)pagingView:(PagingView *)thePagingView reusableViewForPageIndex:(NSUInteger)thePageIndex withFrame:(CGRect)theFrame

Here you provide view to PagingView and it adds your view beneath your last view, messing up your view hierarchy. I think you might use some kind of a flag somewhere to prevent PagingView from requesting new views. The strangest thing is that this kind of behavior happens only for last view. Hope this will help you.

EDIT:

I found the problem. In the method

- (void)layoutSubviewsFromIndex:(NSUInteger)theFromIndex toIndex:(NSUInteger)theToIndex

indexes of the leftmost and rightmost visible views are computed. But the rightmost visible index is mistakenly computed as the leftmost index, so, you need to replace this line:

NSUInteger theRightMostPageIndex = (NSUInteger)floorf(CGRectGetMinX(theRightMostReusableView.frame) / thePageWidth);

With this one:

NSUInteger theRightMostPageIndex = MIN((NSInteger)floorf((CGRectGetMaxX(theRightMostReusableView.frame) - 0.1f) / thePageWidth), MAX(0, self.numberOfItems - 1));

This will prevent PagingView from requesting new views for already visible last (rightmost) view.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM