9

I have an iPad App where I'm using a UICollectionView and each UICollectionViewCell contains just a single UIImage. Currently I'm displaying per 9 UIImages (3 rows * 3 columns) per page, I have several pages.

I would like to use Pinch Gesture to zoom on the entire UICollectionView to increase/decrease the number of row/columns displayed per page and the best would be to have beautiful zoom animation during the Pinch gesture!

Currently, I have added a Pinch Gesture on my UICollectionView. I catch the Pinch Gesture event to compute the number of rows/columns using the scale factor, if it has changed then I update the full UICollectionView using:

[_theCollectionView performBatchUpdates:^{
     [_theCollectionView deleteSections:[NSIndexSet indexSetWithIndex:0]];
     [_theCollectionView insertSections:[NSIndexSet indexSetWithIndex:0]];
 } completion:nil];

It works but I don't have smooth animation during the transition.

Any idea? UICollectionView inherits from UIScrollView, is there a possibility to re-use the UIScrollView Pinch gesture feature to reach my goal?

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
sebastien
  • 2,489
  • 5
  • 26
  • 47

3 Answers3

31

I'm assuming you're using the default UICollectionViewDelegateFlowLayout, right? Then make sure you respond accordingly to the delegate methods, and when the pinch gesture occurs, simply invalidate the layout.

For example, if I want to adjust the size of every item, while pinching:

@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>

@property (nonatomic,assign) CGFloat scale;
@property (nonatomic,weak)   IBOutlet UICollectionView *collectionView;

@end

@implementation ViewController
- (void)viewDidLoad
{
    [super viewDidLoad];

    self.scale = 1.0;

    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];

    UIPinchGestureRecognizer *gesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(didReceivePinchGesture:)];
    [self.collectionView addGestureRecognizer:gesture];

}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(50*self.scale, 50*self.scale);
}

- (void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture
{
    static CGFloat scaleStart;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        scaleStart = self.scale;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        self.scale = scaleStart * gesture.scale;
        [self.collectionView.collectionViewLayout invalidateLayout];
    }
}

The property self.scale is just for show, you can apply this same concept to any other attribute, this doesn't require a beginUpdates/endUpdates because the user himself is carrying the timing of the scale.

Here's a running project, in case you want to see it in action.

Can
  • 8,502
  • 48
  • 57
  • +1 Thanks! Checked your demo and its really useful. Can you guide on how we can show these transitions with a nice animation...I am a newbie to UICollectionView. – Yogi Jul 31 '13 at 13:33
  • 1
    Great answer, curious if you have any insight on what to do if the layout is more 'fixed' and you want to zoom in / out and pan around (not move the items). Think of a map for instance with items on the map being UICollectionCells and tiles potentially being reusableviews or similar. – MobileVet Oct 29 '13 at 18:54
  • 1
    @MobileVet For my thesis I wrote a custom UICollectionViewLayout that creates something like a map, and its entirely possible. If you make the content size that the collection view layout reports larger with a scale factor, then you can have both zooming and panning. – Can Oct 31 '13 at 18:20
  • That inadvertently kind of makes the content much much bigger than it needs to be, right? :/ – fatuhoku Apr 23 '14 at 12:54
  • @fatuhoku well, yes. What's your concern? – Can Apr 23 '14 at 16:11
1

Sorry for my 2 cents question, I have found the solution, very simple.

In my PinchGesture callback I have just done the following:

void (^animateChangeWidth)() = ^() {
    _theFlowLayout.itemSize = cellSize;
};

[UIView transitionWithView:self.theCollectionView 
                  duration:0.1f 
                   options:UIViewAnimationOptionCurveLinear 
                animations:animateChangeWidth 
                completion:nil];

All cells of my UICollectionView are successfully changed and with a nice transition.

Alex Cio
  • 6,014
  • 5
  • 44
  • 74
sebastien
  • 2,489
  • 5
  • 26
  • 47
  • 4
    That's not the proper way to handle scaling, you're basically forcing a redraw of the whole collectionView, when that feature is already supported by layout invalidation `[collectionView.collectionViewLayout invalidateLayout]`. I left an answer with a more thorough explanation and example. – Can Jul 08 '13 at 02:05
  • Please, can you add more details on this? – sebastien Jul 08 '13 at 02:51
0

For Xamarin.iOS developers I found this solution: add a UIScrollView element to the main view and add the UICollectionView as an element of the UIScrollView. Then create a zoom delegate for the UIScrollView.

MainScrollView = new UIScrollView(new CGRect(View.Frame.X, View.Frame.Y, size.Width, size.Height));

        
        _cellReuseId = GenCellReuseId();

        _contentScroll = new UICollectionView(new CGRect(View.Frame.X, View.Frame.Y, size.Width, size.Height), new InfiniteScrollCollectionLayout(size.Width, size.Height));
        
        _contentScroll.AllowsSelection = true;
        _contentScroll.ReloadData();


        _contentScroll.Center = MainScrollView.Center;
        _contentScroll.Frame = new CGRect(_contentScroll.Frame.X, _contentScroll.Frame.Y - 32, _contentScroll.Frame.Width, _contentScroll.Frame.Height);
        MainScrollView.ContentSize = _contentScroll.ContentSize;
        MainScrollView.AddSubview(_contentScroll);
        MainScrollView.MaximumZoomScale = 4f;
        MainScrollView.MinimumZoomScale = 1f;
        MainScrollView.BouncesZoom = true;
        MainScrollView.ViewForZoomingInScrollView += (UIScrollView sv) =>
        {
            if (_contentScroll.Frame.Height < sv.Frame.Height && _contentScroll.Frame.Width < sv.Frame.Width)
            {
                _contentScroll.Center = MainScrollView.Center;
                _contentScroll.Frame = new CGRect(_contentScroll.Frame.X, _contentScroll.Frame.Y - 64, _contentScroll.Frame.Width, _contentScroll.Frame.Height);
                _contentScroll.BouncesZoom = true;
                _contentScroll.AlwaysBounceHorizontal = false;
            }
            return _contentScroll;
};
Ders
  • 201
  • 2
  • 5