I’ve a horizontally scrolling UICollectionView
in Swift.
Inside scrollViewDidScroll
, I apply a rotation and scale remodel 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 information.
It looks like making use of a remodel in scrollViewDidScroll
interferes with the gathering view’s contact dealing with and hit-testing, however I can’t determine methods to stop it with out eradicating the remodel impact.
How can I maintain the remodel impact and forestall 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 screen
let centerX = view.bounds.width / 2
let cellCenter = collectionView.convert(cell.heart, to: view)
let distance = centerX - cellCenter.x
// 2. Calculate rotation and scale primarily based 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 remodel
UIView.animate(withDuration: 0.3, delay: 0, choices: [.beginFromCurrentState, .allowUserInteraction], animations: {
cell.remodel = 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.most important.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.dimension.width
let currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
if currentPage == 0 {
let indexPath = IndexPath(merchandise: infinitePlaceholderArray.depend - 2, part: 0)
cardsCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
// Delay snapping till format is corrected
DispatchQueue.most important.asyncAfter(deadline: .now() + 0.01) {
self.snapToNearestCell()
UIView.animate(withDuration: 0.3) {
self.applyTransformToVisibleCells()
}
}
return
} else if currentPage == infinitePlaceholderArray.depend - 1 {
let indexPath = IndexPath(merchandise: 1, part: 0)
cardsCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
// Delay snapping
DispatchQueue.most important.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() {
// Be sure you have information 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.heart, 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.remodel = CGAffineTransform(rotationAngle: angle).scaledBy(x: scale, y: scale)
}
}
personal func snapToNearestCell() {
guard let collectionView = cardsCollectionView else { return }
let centerX = collectionView.bounds.dimension.width / 2 + collectionView.contentOffset.x
var closestIndexPath: IndexPath?
var closestDistance: CGFloat = .greatestFiniteMagnitude
for cell in collectionView.visibleCells {
let cellCenterX = cell.heart.x
let distance = abs(cellCenterX - centerX)
if distance
I attempted:
-
Detecting a number of touches in
touchesBegan
and calling mysnapToNearestCell()
methodology to power snapping. -
Briefly disabling
isUserInteractionEnabled
on the gathering view throughout snap animations. -
Forcing
scrollViewWillEndDragging
andscrollViewDidEndDecelerating
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 consequence:
After a double-tap or two-finger contact whereas scrolling, the gathering view turns into caught. Scrolling stops working solely, and I’ve to reload the gathering view to revive interplay.