[ios] 화면이 아닌 셀별로 UICollectionView 페이징

나는이 UICollectionView가로 스크롤로와 나란히 전체 화면 당이 세포는 항상 존재한다. 셀의 시작 부분에서 멈추려면 스크롤이 필요합니다. 페이징을 사용하면 콜렉션보기가 한 번에 2 셀인 전체 페이지를 스크롤 한 다음 중지합니다.

단일 셀로 스크롤하거나 셀 가장자리에서 멈춘 상태에서 여러 셀로 스크롤 할 수 있어야합니다.

하위 클래스를 UICollectionViewFlowLayout만들고 메서드를 구현 하려고 targetContentOffsetForProposedContentOffset했지만 지금까지는 컬렉션 뷰만 깨뜨릴 수 있었고 스크롤이 중지되었습니다. 이것을 달성하는 더 쉬운 방법이 UICollectionViewFlowLayout있습니까? 아니면 서브 클래스의 모든 메소드를 구현해야 합니까? 감사.



답변

좋아, 그래서 여기에서 해결책을 찾았습니다 : UICollectionViewFlowLayout을 서브 클래 싱하지 않고 targetContentOffsetForProposedContentOffset : withScrollingVelocity

나는 targetContentOffsetForProposedContentOffset처음에 검색 했어야했다 .


답변

메소드를 재정의하십시오.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    *targetContentOffset = scrollView.contentOffset; // set acceleration to 0.0
    float pageWidth = (float)self.articlesCollectionView.bounds.size.width;
    int minSpace = 10;

    int cellToSwipe = (scrollView.contentOffset.x)/(pageWidth + minSpace) + 0.5; // cell width + min spacing for lines
    if (cellToSwipe < 0) {
        cellToSwipe = 0;
    } else if (cellToSwipe >= self.articles.count) {
        cellToSwipe = self.articles.count - 1;
    }
    [self.articlesCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:cellToSwipe inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
}


답변

사용자 정의 페이지 너비를 사용한 수평 페이징 (Swift 4 및 5)

여기에 제시된 많은 솔루션은 제대로 구현 된 페이징처럼 느껴지지 않는 이상한 동작을 초래합니다.


그러나이 튜토리얼에 제시된 솔루션 에는 문제가없는 것 같습니다. 완벽하게 작동하는 페이징 알고리즘처럼 느껴집니다. 5 가지 간단한 단계로 구현할 수 있습니다.

  1. 유형에 다음 특성을 추가하십시오. private var indexOfCellBeforeDragging = 0
  2. 다음 collectionView delegate과 같이 설정하십시오 .collectionView.delegate = self
  3. UICollectionViewDelegate확장 을 통해 준수 추가 :extension YourType: UICollectionViewDelegate { }
  4. UICollectionViewDelegate적합성을 구현하는 확장에 다음 메소드를 추가하고에 대한 값을 설정하십시오 pageWidth.

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        let pageWidth = // The width your page should have (plus a possible margin)
        let proportionalOffset = collectionView.contentOffset.x / pageWidth
        indexOfCellBeforeDragging = Int(round(proportionalOffset))
    }
  5. UICollectionViewDelegate준수를 구현하는 확장에 다음 메소드를 추가하고에 대해 동일한 값을 설정하고 pageWidth(이 값을 중앙에 저장할 수도 있음)에 대한 값을 설정하십시오 collectionViewItemCount.

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        // Stop scrolling
        targetContentOffset.pointee = scrollView.contentOffset
    
        // Calculate conditions
        let pageWidth = // The width your page should have (plus a possible margin)
        let collectionViewItemCount = // The number of items in this section
        let proportionalOffset = collectionView.contentOffset.x / pageWidth
        let indexOfMajorCell = Int(round(proportionalOffset))
        let swipeVelocityThreshold: CGFloat = 0.5
        let hasEnoughVelocityToSlideToTheNextCell = indexOfCellBeforeDragging + 1 < collectionViewItemCount && velocity.x > swipeVelocityThreshold
        let hasEnoughVelocityToSlideToThePreviousCell = indexOfCellBeforeDragging - 1 >= 0 && velocity.x < -swipeVelocityThreshold
        let majorCellIsTheCellBeforeDragging = indexOfMajorCell == indexOfCellBeforeDragging
        let didUseSwipeToSkipCell = majorCellIsTheCellBeforeDragging && (hasEnoughVelocityToSlideToTheNextCell || hasEnoughVelocityToSlideToThePreviousCell)
    
        if didUseSwipeToSkipCell {
            // Animate so that swipe is just continued
            let snapToIndex = indexOfCellBeforeDragging + (hasEnoughVelocityToSlideToTheNextCell ? 1 : -1)
            let toValue = pageWidth * CGFloat(snapToIndex)
            UIView.animate(
                withDuration: 0.3,
                delay: 0,
                usingSpringWithDamping: 1,
                initialSpringVelocity: velocity.x,
                options: .allowUserInteraction,
                animations: {
                    scrollView.contentOffset = CGPoint(x: toValue, y: 0)
                    scrollView.layoutIfNeeded()
                },
                completion: nil
            )
        } else {
            // Pop back (against velocity)
            let indexPath = IndexPath(row: indexOfMajorCell, section: 0)
            collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
        }
    }

답변

Evya의 답변의 Swift 3 버전 :

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
  targetContentOffset.pointee = scrollView.contentOffset
    let pageWidth:Float = Float(self.view.bounds.width)
    let minSpace:Float = 10.0
    var cellToSwipe:Double = Double(Float((scrollView.contentOffset.x))/Float((pageWidth+minSpace))) + Double(0.5)
    if cellToSwipe < 0 {
        cellToSwipe = 0
    } else if cellToSwipe >= Double(self.articles.count) {
        cellToSwipe = Double(self.articles.count) - Double(1)
    }
    let indexPath:IndexPath = IndexPath(row: Int(cellToSwipe), section:0)
    self.collectionView.scrollToItem(at:indexPath, at: UICollectionViewScrollPosition.left, animated: true)


}


답변

수평 스크롤을 위해 Swift 4.2 에서 내가 찾은 가장 쉬운 방법은 다음과 같습니다 .

첫 번째 셀을 사용하고 visibleCells다음으로 스크롤합니다. 첫 번째 표시되는 셀이 너비의 절반보다 적게 표시되면 다음 셀로 스크롤합니다.

컬렉션은 스크롤하면 수직으로 , 간단하게 변경 x에 의해 ywidth에 의해height

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
    var indexes = self.collectionView.indexPathsForVisibleItems
    indexes.sort()
    var index = indexes.first!
    let cell = self.collectionView.cellForItem(at: index)!
    let position = self.collectionView.contentOffset.x - cell.frame.origin.x
    if position > cell.frame.size.width/2{
       index.row = index.row+1
    }
    self.collectionView.scrollToItem(at: index, at: .left, animated: true )
}


답변

StevenOjo의 답변을 부분적으로 기반으로합니다. 가로 스크롤을 사용하고 Bounce UICollectionView를 사용하지 않고 이것을 테스트했습니다. cellSize는 CollectionViewCell 크기입니다. 요소를 조정하여 스크롤 감도를 수정할 수 있습니다.

override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
    var factor: CGFloat = 0.5
    if velocity.x < 0 {
        factor = -factor
    }
    let indexPath = IndexPath(row: (scrollView.contentOffset.x/cellSize.width + factor).int, section: 0)
    collectionView?.scrollToItem(at: indexPath, at: .left, animated: true)
}


답변

다음 은 수직 셀 기반 페이징을 위한 Swift 5의 구현입니다 .

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    guard let collectionView = self.collectionView else {
        let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
        return latestOffset
    }

    // Page height used for estimating and calculating paging.
    let pageHeight = self.itemSize.height + self.minimumLineSpacing

    // Make an estimation of the current page position.
    let approximatePage = collectionView.contentOffset.y/pageHeight

    // Determine the current page based on velocity.
    let currentPage = velocity.y == 0 ? round(approximatePage) : (velocity.y < 0.0 ? floor(approximatePage) : ceil(approximatePage))

    // Create custom flickVelocity.
    let flickVelocity = velocity.y * 0.3

    // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
    let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

    let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - collectionView.contentInset.top

    return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}

몇 가지 참고 사항 :

  • 결함이 없습니다
  • 페이징을 거짓으로 설정하십시오 ! (그렇지 않으면 작동하지 않습니다)
  • 자신 만의 flickvelocity를 쉽게 설정할 있습니다.
  • 이 작업을 시도한 후에도 여전히 작동하지 않는 경우 itemSize문제가 자주 발생하므로 항목의 크기와 실제로 일치 하는지 확인하세요 . 특히를 사용할 때 collectionView(_:layout:sizeForItemAt:)대신 itemSize와 함께 맞춤 변수를 사용하세요.
  • 을 설정할 때 가장 잘 작동합니다 self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

다음은 수평 버전입니다 (완전히 테스트하지 않았으므로 실수를 용서하십시오).

override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    guard let collectionView = self.collectionView else {
        let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
        return latestOffset
    }

    // Page width used for estimating and calculating paging.
    let pageWidth = self.itemSize.width + self.minimumInteritemSpacing

    // Make an estimation of the current page position.
    let approximatePage = collectionView.contentOffset.x/pageWidth

    // Determine the current page based on velocity.
    let currentPage = velocity.x == 0 ? round(approximatePage) : (velocity.x < 0.0 ? floor(approximatePage) : ceil(approximatePage))

    // Create custom flickVelocity.
    let flickVelocity = velocity.x * 0.3

    // Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
    let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)

    // Calculate newHorizontalOffset.
    let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left

    return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
}

이 코드는 내 개인 프로젝트에서 사용하는 코드를 기반으로합니다. 여기 에서 다운로드하고 예제 대상을 실행하여 확인할 수 있습니다 .