[英]UICollectionView horizontal paging layout
Hy, I'm trying to achieve this UI element, that it seems (to me) like an horizontal UIPickerView. 嗨,我正在尝试实现这个UI元素,在我看来,它看起来像是水平的UIPickerView。 Here is an example GIF from when creating a "memoji" on iOS:
这是在iOS上创建“表情符号”时的GIF示例:
I have been trying to accomplish this with UICollectionView and a custom UICollectionViewFlowLayout. 我一直在尝试使用UICollectionView和自定义UICollectionViewFlowLayout来实现。 But without much luck.
但是运气不好。
What I tried so far is using 到目前为止,我尝试使用的是
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
To stop the scrolling on each cell, thus giving it a sense of paging. 停止在每个单元格上的滚动,从而使其具有分页感。 But in order to do that I actually have to make the
但是为了做到这一点,我实际上必须使
var collectionViewContentSize: CGSize
Return a way higher content size than it actually exists, otherwise it would just bounce the collectionView and nothing would snap into place no matter what I returned on the previous function. 返回一个比实际大小更高的内容大小,否则它将使collectionView弹跳,无论我在上一个函数中返回的内容如何,都不会对齐。
I also tried using 我也尝试使用
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
To set the collectionView.contentOffset
but that was causing weird jumps in the animation and again it was not changing this properly. 要设置
collectionView.contentOffset
但是这会导致动画中出现怪异的跳转,并且再次没有正确地更改此设置。 Besides the paging per cell I would like to achieve whats on that UI element, a small Haptic Feedback on each scroll when passing trough the elements and the fade in and out of left and right elements on the border. 除了每个单元的分页之外,我还想实现该UI元素的功能,在通过该元素以及边框上左右元素的淡入和淡出时,每个滚动上都有一个小的触觉反馈。 If anyone could point me in the right direction, maybe UICollectionView is not the way to go?
如果有人可以向我指出正确的方向,也许UICollectionView不是路要走? I would appreciate a lot.
我将不胜感激。 Thank you
谢谢
I was able to achieve this using a custom UICollectionViewFlowLayout
我能够使用自定义
UICollectionViewFlowLayout
实现此UICollectionViewFlowLayout
final class PaginatedCollectionViewFlow: UICollectionViewFlowLayout {
/* Distance from the midle to the other side of the screen */
var availableDistance: CGFloat = 0.0
var midX: CGFloat = 0
var lastElementIndex = 0
let maxAngle = CGFloat(-60.0.degree2Rad)
override func prepare () {
minimumInteritemSpacing = 40.0
scrollDirection = .horizontal
}
/* This should be cached */
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard
let layoutAttributes = super.layoutAttributesForElements(in: rect),
let cv = collectionView else { return nil }
/* Size of the collectionView */
let visibleRect = CGRect(origin: cv.contentOffset, size: cv.bounds.size)
let attributes: [UICollectionViewLayoutAttributes] = layoutAttributes.compactMap { attribute in
guard let copy = attribute.copy() as? UICollectionViewLayoutAttributes else { return nil }
/* Distance from the middle of the screen to the middle of the cell attributes */
let distance = visibleRect.midX - attribute.center.x
/* Normalize the distance between [0, 1] */
let normalizedDistance = abs(distance / availableDistance)
/* Rotate the cell and apply alpha accordingly to the maximum distance from the center */
copy.alpha = 1.0 - normalizedDistance
copy.transform3D = CATransform3DMakeRotation(maxAngle * normalizedDistance, 0, 1, 0)
return copy
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Make sure to set the UICollectionViewFlowLayout
parameters after adding the UICollectionView: 确保在添加UICollectionView之后设置
UICollectionViewFlowLayout
参数:
guard let flow = collectionView.collectionViewLayout as? PaginatedCollectionViewFlow else { return }
/* Distance from the middle to the other side of the screen */
flow.availableDistance = floor(view.bounds.width / 2.0)
/* Middle of the screen */
flow.midX = ceil(view.bounds.midX)
/* Index of the last element in the collectionView */
flow.lastElementIndex = vm.numberOfItems - 1
/* Left and Right Insets */
flow.sectionInset.left = flow.midX - 30.0
flow.sectionInset.right = flow.midX - 30.0
And finally after conforming to UICollectionViewDelegate
to get the UIScrollView
delegate methods: 最后,在遵循
UICollectionViewDelegate
以获取UIScrollView
委托方法之后:
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollToPosition(scrollView: scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
guard !decelerate else { return }
scrollToPosition(scrollView: scrollView)
}
internal func scrollToPosition(scrollView: UIScrollView) {
guard let ip = indexPathForCenterCell else { return }
scrollToIndex(ip.row, animated: true)
}
internal func scrollToIndex(_ index: Int, animated: Bool) {
let ip = IndexPath(item: index, section: 0)
guard let attributes = collectionView.layoutAttributesForItem(at: ip) else { return }
let halfWidth = collectionView.frame.width / CGFloat(2.0)
let offset = CGPoint(x: attributes.frame.midX - halfWidth, y: 0)
collectionView.setContentOffset(offset, animated: animated)
guard let cell = collectionView.cellForItem(at: ip) else { return }
feedbackGenerator.selectionChanged()
cell.isHighlighted = true
collectionView.visibleCells.filter { $0 != cell }.forEach { $0.isHighlighted = false }
}
internal var indexPathForCenterCell: IndexPath? {
let point = collectionView.convert(collectionView.center, from: collectionView.superview)
guard let indexPath = collectionView.indexPathForItem(at: point) else { return collectionView.indexPathsForVisibleItems.first }
return indexPath
}
/* Gets the CGSize based of a maximum size available for the provided String */
func sizeFor(text: String) -> CGSize {
guard let font = UIFont(font: .sanFranciscoSemiBold, size: 15.0) else { return .zero }
let textNS = text as NSString
let maxSize = CGSize(width: collectionView.frame.width / 2, height: collectionView.frame.height)
let frame = textNS.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context: nil)
return frame.size
}
This provided Pagination of the UICollectionViewCells while "snapping" it to the nearest cell and also using UISelectionFeedbackGenerator
to generate haptic feedback. 这提供了UICollectionViewCells的分页功能,同时将其“捕捉”到最近的单元格,还使用
UISelectionFeedbackGenerator
生成触觉反馈。 Hope this helps someone with the same problem I had. 希望这对遇到同样问题的人有所帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.