iosuicollectionviewzoominguipinchgesturerecognizer

Delay in zoom animation UICollectionView with UIPinchGestureRecognizer


Please, tell me the best way to "zoom in/out" UICollectionView and change size of UICollectionViewCell. I'm using a UIPinchGestureRecognizer now: When UICollectionView is "zoom in" and "zoom out", there are serious delays and freezing.

How can I get rid of friezes when I perform a PinchGestureRecognizer?

Here is implementation code of UIPinchGestureRecognizer in UICollectionViewController:

class BlocksCollectionViewController: UICollectionViewController, UIGestureRecognizerDelegate {

  private var senderStartSize = CGFloat()
  private var senderNormalPosition = CGSize()

  override func viewDidLoad() {
    super.viewDidLoad()

    let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
    pinchRecognizer.delegate = self
    collectionView?.addGestureRecognizer(pinchRecognizer)
  }

  @objc func handlePinch(_ sender: UIPinchGestureRecognizer) {
    guard let collection = collectionView else { return }
    guard let layout = collection.collectionViewLayout as? CustomCollectionViewLayout else { return }

    switch sender.state {
    case .began:

      senderStartSize = layout.cellSize

    case .changed:

      layout.cellSize = senderStartSize * sender.scale
      layout.invalidateLayout()

    default:
      break
    }
  }

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }
}

The code below is сustom CollectionViewLayout:

class CustomCollectionViewLayout: UICollectionViewLayout {

  private var cellAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
  var cellSize: CGFloat = 7
  private var contentSize = CGSize.zero

  override var collectionViewContentSize: CGSize {
    return self.contentSize
  }

  override func prepare() {
    guard let collection = collectionView else { return }

    for section in 0...collection.numberOfSections - 2 {
      for item in 0...collection.numberOfItems(inSection: section) - 2 {
        let cellIndex = IndexPath(item: item, section: section)
        let xPos = CGFloat(item) * cellSize
        let yPos = CGFloat(section) * cellSize

        let attributes = UICollectionViewLayoutAttributes(forCellWith: cellIndex)
        attributes.frame = CGRect(x: xPos, y: yPos, width: cellSize, height: cellSize)

        cellAttributes[cellIndex] = attributes
      }
    }

    let contentWidth = CGFloat(collection.numberOfItems(inSection: 0)) * cellSize
    let contentHeight = CGFloat(collection.numberOfSections) * cellSize
    contentSize = CGSize(width: contentWidth, height: contentHeight)

    let centerOffsetX = (collection.contentSize.width - collection.frame.size.width) / 2
    let centerOffsetY = (collection.contentSize.height - collection.frame.size.height) / 2
    let centerPoint = CGPoint(x: centerOffsetX, y: centerOffsetY)

    collection.setContentOffset(centerPoint, animated: false)
  }

  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    var attributesInRect = [UICollectionViewLayoutAttributes]()

    for cellAttributes in cellAttributes.values {
      if rect.intersects(cellAttributes.frame) {
        attributesInRect.append(cellAttributes)
      }
    }
    return attributesInRect
  }

  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cellAttributes[indexPath]
  }
}

result of this code (GIF)


Solution

  • Due to the fact that there are a lot of UICollectionViewCells in my collection, the best way is this:

    1) Place UICollectionView on a containerView (simple UIView), and put this containerView on UIScrollView.

    I limited zoom:

    scrollView.minimumZoomScale = 1
    scrollView.maximumZoomScale = 6
    

    2) And used functions of UIScrollViewDelegate:

      func scrollViewDidZoom(_ scrollView: UIScrollView) {
       // Code for processing zoom - scrollView.zoomScale,
       // contentOffset, contentSize etc....
      }
    
      func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return containerView
      }
    

    Result of this solution