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:
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.