简体   繁体   English

UICollectionView水平分页布局

[英]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示例:

Example GIF 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM