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:
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));