1
votes

I have a requirement to support zoom in UICollectionView.

Requirements:

  1. After zoom in, it has to support to view the UICollectionViewCell’s hidden area ( area out of viewport) by horizontal and vertical scroll.
  2. After Zoom out/in, it has to support the selection of UICollectionViewCell and able to scroll the UICollectionView ( Basically the default UICollectionView behavior on going back to no zoom state. ).

The list of approaches tried:

  1. Added GestureRecognizer

a. Added UIPinchGestureRecognizer to transform the UICollectionView by scale. b. After Zoom in, it was not possible to move the UICollectionViewcell to view the hidden area. c. Added UIPanGestureRecognizer to move the center of UICollectionView d. It was working fine to move the UICollectionView. e. Now we can’t able to select the UICollectionViewCell and can’t able to scroll UICollectionView.

  1. Added UICollectionView inside UIScrollView

a. Added UIScrollView with delegates. b. Added UICollectionView as sub view of UIScrollView c. Zoom out is not happening because UICollectionView (inherited by UIScrollView) consumes the zoom gesture

  1. Added UIColectionView and UIScrollView both as siblings

a. Added UIScrollView and UICollectionView to parent. b. Bring UIScrollView to front. c. Zoom is working but not able to pan to see the hidden area.

Please suggest if there any way to fix above approaches or a better strategy to achieve zoom in a collectionView.

1
At the end of the day, your UICollectionView has to scroll either horizontally or vertically. When you zoom into it, it won't be able to scroll in both directions. I think your zoom feature will basically have to be changing the size or the top of cell that you show based on how zoomed in you are. You could resize your cells in the sizeForCell function or maybe you could have multiple Collection View Cell classes and show the relevant one based on the zoom level you want - Alan S

1 Answers

0
votes

I have solved this using a UIScrollView and a UICollectionViewLayout subclass.

1) place a UIScrollView on top of the UICollectionView with the same frame.

 self.view.addSubview(scrollView)
 scrollView.addSubview(dummyViewForZooming)
 scrollView.frame = collectionView.frame
 scrollView.bouncesZoom = false
 scrollView.minimumZoomScale = 0.5
 scrollView.maximumZoomScale = 3.0

2) Set the contentSize of the UIScrollView and zoomingView to be the same as the UICollectionView

  override func viewDidLayoutSubviews() {
    super.viewWillLayoutSubviews()

    scrollView.contentSize = layout.collectionViewContentSize
    dummyViewForZooming.frame = CGRect(origin: .zero, size: layout.collectionViewContentSize)
    scrollView.frame = collectionView.frame
  }

3) Remove all gesture recognizers from the UICollectionView and add a delegate for the UIScrollView. Add a tap gesture recognizer to the UIScrollview

    collectionView.gestureRecognizers?.forEach {

        collectionView.removeGestureRecognizer($0)

    }
    let tap = UITapGestureRecognizer.init(target: self, action: #selector(scrollViewWasTapped(sender:)))
    tap.numberOfTapsRequired = 1
    scrollView.addGestureRecognizer(tap)


    scrollView.delegate = self

4) When the ScrollView scrolls or zooms, set the contentOffset of the UICollectionView to be the same as the ScrollView contentOffset, set the layoutScale of your UICollectionViewLayout as the zoomscale and invalidate the layout.

func scrollViewDidZoom(_ scrollView: UIScrollView) {

    if let layout = self.layout, layout.getScale() != scrollView.zoomScale {
        layout.layoutScale = scrollView.zoomScale
        self.layout.invalidateLayout()
        collectionView.contentOffset = scrollView.contentOffset
    }
}

func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return dummyViewForZooming
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    collectionView.contentOffset = scrollView.contentOffset

}

5) override the prepare method in the UICollectionViewLayout, scan through all your layoutAttributes and set a transform:

    attribute.transformedFrame = attribute.originalFrame.scale(layoutScale)
    let ts = CGAffineTransform(scaleX: layoutScale, y: layoutScale)
        attribute.transform = ts

    let xDifference = attribute.frame.origin.x - attribute.transformedFrame.origin.x
    let yDifference = attribute.frame.origin.y - attribute.transformedFrame.origin.y

    let t1 = CGAffineTransform(translationX: -xDifference, y: -yDifference)
    let t = ts.concatenating(t1)
        attribute.transform = t

6) ensure you scale the collectionView content size:

override var collectionViewContentSize: CGSize  {
        return CGSize(width: width * layoutScale, height: height * layoutScale)
    } 

7) Intercept taps from the tap gesture recognizer and convert the location in view to a point in the collection view, you can then get the indexPath of that cell using indexPathForItem(point:) and select the cell or pass on events to the underlying views of the cell etc..

hope this helps