ios – UICollectionView freezes after double-tap on a rotated/scaled cell throughout horizontal scrolling

0
1
ios – UICollectionView freezes after double-tap on a rotated/scaled cell throughout horizontal scrolling


I’ve a horizontally scrolling UICollectionView in Swift.
Inside scrollViewDidScroll, I apply a rotation and scale rework to seen cells to create a card-tilt impact.

The difficulty:
If I double-tap or use two fingers on the gathering view throughout scrolling, the gathering view turns into “frozen” — it stops responding to swipe gestures, and the centered cell stays caught in place till I reload knowledge.

It looks as if making use of a rework in scrollViewDidScroll interferes with the gathering view’s contact dealing with and hit-testing, however I can’t work out easy methods to forestall it with out eradicating the rework impact.

How can I preserve the rework impact and stop the gathering view from freezing after multi-touch or double-tap?

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        guard let collectionView = scrollView as? UICollectionView else { return }
        
        for cell in collectionView.visibleCells {
            // 1. Calculate the cell's horizontal distance from the middle of the display
            let centerX = view.bounds.width / 2
            let cellCenter = collectionView.convert(cell.middle, to: view)
            let distance = centerX - cellCenter.x
            
            // 2. Calculate rotation and scale based mostly on this distance
            // The farther from the middle, the extra it rotates and shrinks.
            let maxDistance = collectionView.bounds.width / 2
            let normalizedDistance = distance / maxDistance // Worth from -1 to 1
            
            let maxAngle = CGFloat.pi / 30 // A refined angle (e.g., 6 levels)
            let angle = maxAngle * normalizedDistance
            
            let minScale: CGFloat = 0.9
            let scale = 1.0 - (abs(normalizedDistance) * (1.0 - minScale))
            
            // 3. Apply the rework
            UIView.animate(withDuration: 0.3, delay: 0, choices: [.beginFromCurrentState, .allowUserInteraction], animations: {
                cell.rework = CGAffineTransform(rotationAngle: angle).scaledBy(x: scale, y: scale)
            }, completion: nil)
            
        }
        // ✅ Set preliminary centered index as soon as after format go
        if !hasSetInitialCenteredIndex {
            hasSetInitialCenteredIndex = true
            DispatchQueue.predominant.asyncAfter(deadline: .now() + 0.05) {
                self.snapToNearestCell()
                self.applyTransformToVisibleCells()
            }
        }
    }
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        
        self.cardsCollectionView.isUserInteractionEnabled = false
        
        isScrolling = true
        
        let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView)
        currentScrollDirection = velocity.x == 0 ? 0 : (velocity.x > 0 ? 1 : -1)
        
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        
        self.cardsCollectionView.isUserInteractionEnabled = true
        
        isScrolling = false
        
        let pageWidth = scrollView.body.measurement.width
        let currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
        
        if currentPage == 0 {
            let indexPath = IndexPath(merchandise: infinitePlaceholderArray.rely - 2, part: 0)
            cardsCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
            
            // Delay snapping till format is corrected
            DispatchQueue.predominant.asyncAfter(deadline: .now() + 0.01) {
                self.snapToNearestCell()
                UIView.animate(withDuration: 0.3) {
                    self.applyTransformToVisibleCells()
                }
            }
            return
            
        } else if currentPage == infinitePlaceholderArray.rely - 1 {
            let indexPath = IndexPath(merchandise: 1, part: 0)
            cardsCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
            
            // Delay snapping
            DispatchQueue.predominant.asyncAfter(deadline: .now() + 0.01) {
                self.snapToNearestCell()
                UIView.animate(withDuration: 0.3) {
                    self.applyTransformToVisibleCells()
                }
            }
            return
        }
        
        // No wrapping, snap usually
        snapToNearestCell()
        UIView.animate(withDuration: 0.3) {
            self.applyTransformToVisibleCells()
        }
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            
            self.cardsCollectionView.isUserInteractionEnabled = true
            
            isScrolling = false
            
            snapToNearestCell()
            UIView.animate(withDuration: 0.3) {
                self.applyTransformToVisibleCells()
            }
        }
    }
    
    func setupInfiniteDataSource() {
        // Ensure you have knowledge to work with
        guard !placeholderArray.isEmpty else { return }
        
        // [Last Item] + [All Original Items] + [First Item]
        infinitePlaceholderArray.append(placeholderArray.final!)
        infinitePlaceholderArray.append(contentsOf: placeholderArray)
        infinitePlaceholderArray.append(placeholderArray.first!)
    }
    
    func applyTransformToVisibleCells() {
        guard let collectionView = cardsCollectionView else { return }
        
        for cell in collectionView.visibleCells {
            let centerX = view.bounds.width / 2
            let cellCenter = collectionView.convert(cell.middle, to: view)
            let distance = centerX - cellCenter.x
            
            let maxDistance = collectionView.bounds.width / 2
            let normalizedDistance = distance / maxDistance
            
            let maxAngle = CGFloat.pi / 30
            let angle = maxAngle * normalizedDistance
            
            let minScale: CGFloat = 0.9
            let scale = 1.0 - (abs(normalizedDistance) * (1.0 - minScale))
            
            cell.rework = CGAffineTransform(rotationAngle: angle).scaledBy(x: scale, y: scale)
        }
    }

    non-public func snapToNearestCell() {
        guard let collectionView = cardsCollectionView else { return }
        
        let centerX = collectionView.bounds.measurement.width / 2 + collectionView.contentOffset.x
        
        var closestIndexPath: IndexPath?
        var closestDistance: CGFloat = .greatestFiniteMagnitude
        
        for cell in collectionView.visibleCells {
            let cellCenterX = cell.middle.x
            let distance = abs(cellCenterX - centerX)
            if distance < closestDistance {
                closestDistance = distance
                closestIndexPath = collectionView.indexPath(for: cell)
            }
        }
        
        if let indexPath = closestIndexPath {
            currentlyCenteredIndexPath = indexPath  // Observe centered cell
            collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        }
        
    }
    
    non-public func handleInfiniteScrollWrapping(for scrollView: UIScrollView) {
        let pageWidth = scrollView.body.measurement.width
        let currentPage = Int(flooring((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1)
        
        if currentPage == 0 {
            let targetIndexPath = IndexPath(merchandise: infinitePlaceholderArray.rely - 2, part: 0)
            cardsCollectionView.scrollToItem(at: targetIndexPath, at: .centeredHorizontally, animated: false)
        } else if currentPage == infinitePlaceholderArray.rely - 1 {
            let targetIndexPath = IndexPath(merchandise: 1, part: 0)
            cardsCollectionView.scrollToItem(at: targetIndexPath, at: .centeredHorizontally, animated: false)
        }
    }
}

I attempted:

  • Detecting a number of touches in touchesBegan and calling my snapToNearestCell() technique to pressure snapping.

  • Quickly disabling isUserInteractionEnabled on the gathering view throughout snap animations.

  • Forcing scrollViewWillEndDragging and scrollViewDidEndDecelerating logic to run manually after multi-touch.

Anticipated:
The gathering view ought to snap to the closest cell after a multi-touch occasion and stay scrollable as regular.

Precise end result:
After a double-tap or two-finger contact whereas scrolling, the gathering view turns into caught. Scrolling stops working fully, and I’ve to reload the gathering view to revive interplay.

LEAVE A REPLY

Please enter your comment!
Please enter your name here