[英]UICollectionView horizontal paging layout
嗨,我正在嘗試實現這個UI元素,在我看來,它看起來像是水平的UIPickerView。 這是在iOS上創建“表情符號”時的GIF示例:
我一直在嘗試使用UICollectionView和自定義UICollectionViewFlowLayout來實現。 但是運氣不好。
到目前為止,我嘗試使用的是
func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint
停止在每個單元格上的滾動,從而使其具有分頁感。 但是為了做到這一點,我實際上必須使
var collectionViewContentSize: CGSize
返回一個比實際大小更高的內容大小,否則它將使collectionView彈跳,無論我在上一個函數中返回的內容如何,都不會對齊。
我也嘗試使用
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
要設置collectionView.contentOffset
但是這會導致動畫中出現怪異的跳轉,並且再次沒有正確地更改此設置。 除了每個單元的分頁之外,我還想實現該UI元素的功能,在通過該元素以及邊框上左右元素的淡入和淡出時,每個滾動上都有一個小的觸覺反饋。 如果有人可以向我指出正確的方向,也許UICollectionView不是路要走? 我將不勝感激。 謝謝
我能夠使用自定義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
}
}
確保在添加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
最后,在遵循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
}
這提供了UICollectionViewCells的分頁功能,同時將其“捕捉”到最近的單元格,還使用UISelectionFeedbackGenerator
生成觸覺反饋。 希望這對遇到同樣問題的人有所幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.