화면이 아닌 셀별로 UICollectionView 페이징


113

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

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

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


1
collectionviewcell 너비는 screnn 너비와 같아야하며 collectionView 페이징이 활성화되어 있어야합니다.
Erhan

하지만 한 번에 2 개의 셀을 표시해야합니다. 저는 iPad를 사용하고 있으므로 2 개의 셀이 각각 화면의 절반을 공유합니다.
Martin Koles 2014

2
targetContentOffsetForProposedContentOffset:withScrollingVelocity:페이징 사용 및 해제
Wain

이것이 제가 시도하고있는 것입니다. 어딘가에 예가 있습니까?
Martin Koles 2014

답변:



23

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

- (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];
}

1
이 코드는 저에게 많은 도움이 되었기 때문에 현재 스크롤 방향에 대한 검사를 추가하고 그에 따라 +/- 0.5 값을 조정해야했습니다.
helkarli

1
collectionView.pagingEnabled = true로 설정할 수 있습니다.
evya

@evya 와우 당신이 맞아요. isPagingEnabled가 나를 위해 일했습니다.
BigSauce

@evya 좋은 물건 !!
Anish Kumar

pagingEnabled는 어떻게 작동합니까? 내 오프셋 원래 페이징에서 끝나는 전에 슈퍼 glitchy를 얻을 수
에단 자오에게

17

사용자 정의 페이지 너비를 사용한 수평 페이징 (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)
        }
    }

이것을 사용하는 사람은 Pop back (against velocity)부품을 다음과 같이 변경해야합니다 collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true).. 노트.centeredHorizontally
matthew.kempson

@ matthew.kempson 레이아웃의 동작 방식에 따라 다릅니다. 내가 이것을 사용되는 레이아웃의 경우, .left괜찮다고
fredpi

.left예상대로 작동하지 않는 것으로 나타났습니다 . 너무 멀리 돌아 @fredpi 셀을 밀어 듯
matthew.kempson

13

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)


}

셀의 측면을 클릭하면 이상한 오프셋이 있습니다
Maor

안녕하세요 @Maor, 아직 필요한지 모르겠지만 제 경우에는 컬렉션 뷰에서 페이징을 비활성화하는 문제가 수정되었습니다.
페르난도 마타

2
나는이를 사랑하지만 계정에 속도를 가지고 뭔가를 추가하므로, 빠른 작은 와이프로 조금 부진 느낌과 훨씬 매끄럽게 : if(velocity.x > 1) { mod = 0.5; } else if(velocity.x < -1) { mod = -0.5; }다음 추가 + mod애프터+ Double(0.5)
Captnwalker1

12

수평 스크롤을 위해 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 )
}

소스 기사에 대한 링크를 추가해 주시겠습니까? 그레이트 답변 BTW.
Md. Ibrahim Hassan

@ Md.IbrahimHassan 기사가 없습니다, 내가 소스입니다. Thx
Romulo BM

그것은 작동하지만, 불행히도 경험이 원활하지 않습니다
알라 Eddine Cherbib

부드럽 지 않다는 것은 무엇을 의미합니까? 저에게는 결과가 매우 매끄럽게 애니메이션됩니다 .. 여기에서
Romulo BM

1
이것은 잘 작동합니다
Anuj Kumar Rai

9

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)
}

9

다음 은 수직 셀 기반 페이징을 위한 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)
}

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


1
스위프트 5의 경우 : 사용 .fast대신에UIScollViewDecelerationRateFast
호세

지적 해 주셔서 감사합니다! 이 답변을 업데이트하는 것을 잊고 방금했습니다!
JoniVR

안녕하세요, @JoniVR은 스 와이프가 수직으로 어떻게 작동하는지 보여주는 아주 좋은 설명 예제입니다. 이 작업을 수평 방향으로 완벽하게 만드는 데 필요한 전체 코드 변경 사항을 제안하는 것은 매우 친절 할 것입니다. 위의 코드 외에도 대상 콘텐츠 오프셋 기능에서 수평 스 와이프를 제안했습니다. 정확한 시나리오를 수평 적으로 복제하기 위해 많은 변화가 있었다고 생각합니다. 내가 틀렸다면 정정하십시오.
Shiv Prakash

7

접근 방식 1 : 컬렉션보기

flowLayoutUICollectionViewFlowLayout속성은

override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    if let collectionView = collectionView {

        targetContentOffset.memory = scrollView.contentOffset
        let pageWidth = CGRectGetWidth(scrollView.frame) + flowLayout.minimumInteritemSpacing

        var assistanceOffset : CGFloat = pageWidth / 3.0

        if velocity.x < 0 {
            assistanceOffset = -assistanceOffset
        }

        let assistedScrollPosition = (scrollView.contentOffset.x + assistanceOffset) / pageWidth

        var targetIndex = Int(round(assistedScrollPosition))


        if targetIndex < 0 {
            targetIndex = 0
        }
        else if targetIndex >= collectionView.numberOfItemsInSection(0) {
            targetIndex = collectionView.numberOfItemsInSection(0) - 1
        }

        print("targetIndex = \(targetIndex)")

        let indexPath = NSIndexPath(forItem: targetIndex, inSection: 0)

        collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .Left, animated: true)
    }
}

접근 방식 2 : 페이지보기 컨트롤러

UIPageViewController요구 사항을 충족 하는 경우 사용할 수 있으며 각 페이지에는 별도의 뷰 컨트롤러가 있습니다.


이를 위해 페이징을 비활성화하고 collectionview에서만 스크롤을 활성화해야합니까?
nr5

이것은 최신 swift4 / Xcode9.3에서 작동하지 않으며 targetContentOffset에는 메모리 필드가 없습니다. 스크롤링을 구현했지만 "플릭"할 때 셀 위치를 조정하지 않습니다.
Steven B.

몇 개의 셀로 작동하지만 셀 13에 도달하면 이전 셀로 다시 점프하기 시작하고 계속할 수 없습니다.
Christopher Smit

4

이것은 이것을하는 직접적인 방법입니다.

이 경우는 간단하지만 마지막으로 매우 일반적입니다 (셀 크기가 고정되고 셀 간 간격이 고정 된 일반적인 축소판 스크롤러)

var itemCellSize: CGSize = <your cell size>
var itemCellsGap: CGFloat = <gap in between>

override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let pageWidth = (itemCellSize.width + itemCellsGap)
    let itemIndex = (targetContentOffset.pointee.x) / pageWidth
    targetContentOffset.pointee.x = round(itemIndex) * pageWidth - (itemCellsGap / 2)
}

// CollectionViewFlowLayoutDelegate

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return itemCellSize
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return itemCellsGap
}

scrollToOffset을 호출하거나 레이아웃에 뛰어들 이유가 없습니다. 기본 스크롤 동작은 이미 모든 작업을 수행합니다.

모두 건배 :)


2
선택적 collectionView.decelerationRate = .fast으로 더 가까운 모방 기본 페이징 으로 설정할 수 있습니다 .
elfanek

1
이것은 정말 좋습니다. @elfanek 나는 당신이 작고 부드러운 스 와이프를하지 않으면 제대로 작동하는 설정을 발견했습니다. 그러면 빠르게 깜박이는 것 같습니다.
mylogon

3

evya의 대답과 비슷하지만 targetContentOffset을 0으로 설정하지 않기 때문에 조금 더 부드럽습니다.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    if ([scrollView isKindOfClass:[UICollectionView class]]) {
        UICollectionView* collectionView = (UICollectionView*)scrollView;
        if ([collectionView.collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]) {
            UICollectionViewFlowLayout* layout = (UICollectionViewFlowLayout*)collectionView.collectionViewLayout;

            CGFloat pageWidth = layout.itemSize.width + layout.minimumInteritemSpacing;
            CGFloat usualSideOverhang = (scrollView.bounds.size.width - pageWidth)/2.0;
            // k*pageWidth - usualSideOverhang = contentOffset for page at index k if k >= 1, 0 if k = 0
            // -> (contentOffset + usualSideOverhang)/pageWidth = k at page stops

            NSInteger targetPage = 0;
            CGFloat currentOffsetInPages = (scrollView.contentOffset.x + usualSideOverhang)/pageWidth;
            targetPage = velocity.x < 0 ? floor(currentOffsetInPages) : ceil(currentOffsetInPages);
            targetPage = MAX(0,MIN(self.projects.count - 1,targetPage));

            *targetContentOffset = CGPointMake(MAX(targetPage*pageWidth - usualSideOverhang,0), 0);
        }
    }
}

3

벨로 시티 청취를 위해 Romulo BM 응답 수정

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

    self.collection.scrollToItem(at: index, at: .centeredHorizontally, animated: true )
}

2

스위프트 5

UICollectionView를 서브 클래 싱하지 않고 가로로 contentOffset을 계산하는 방법을 찾았습니다. isPagingEnabled 없이는 분명히 true로 설정됩니다. 다음은 코드입니다.

var offsetScroll1 : CGFloat = 0
var offsetScroll2 : CGFloat = 0
let flowLayout = UICollectionViewFlowLayout()
let screenSize : CGSize = UIScreen.main.bounds.size
var items = ["1", "2", "3", "4", "5"]

override func viewDidLoad() {
    super.viewDidLoad()
    flowLayout.scrollDirection = .horizontal
    flowLayout.minimumLineSpacing = 7
    let collectionView = UICollectionView(frame: CGRect(x: 0, y: 590, width: screenSize.width, height: 200), collectionViewLayout: flowLayout)
    collectionView.register(collectionViewCell1.self, forCellWithReuseIdentifier: cellReuseIdentifier)
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.backgroundColor = UIColor.clear
    collectionView.showsHorizontalScrollIndicator = false
    self.view.addSubview(collectionView)
}

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    offsetScroll1 = offsetScroll2
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    offsetScroll1 = offsetScroll2
}

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>){
    let indexOfMajorCell = self.desiredIndex()
    let indexPath = IndexPath(row: indexOfMajorCell, section: 0)
    flowLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    targetContentOffset.pointee = scrollView.contentOffset
}

private func desiredIndex() -> Int {
    var integerIndex = 0
    print(flowLayout.collectionView!.contentOffset.x)
    offsetScroll2 = flowLayout.collectionView!.contentOffset.x
    if offsetScroll2 > offsetScroll1 {
        integerIndex += 1
        let offset = flowLayout.collectionView!.contentOffset.x / screenSize.width
        integerIndex = Int(round(offset))
        if integerIndex < (items.count - 1) {
            integerIndex += 1
        }
    }
    if offsetScroll2 < offsetScroll1 {
        let offset = flowLayout.collectionView!.contentOffset.x / screenSize.width
        integerIndex = Int(offset.rounded(.towardZero))
    }
    let targetIndex = integerIndex
    return targetIndex
}

1

다음은 Swift 3의 내 버전입니다. 스크롤이 끝난 후 오프셋을 계산하고 애니메이션으로 오프셋을 조정합니다.

collectionLayout 이다 UICollectionViewFlowLayout()

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let index = scrollView.contentOffset.x / collectionLayout.itemSize.width
    let fracPart = index.truncatingRemainder(dividingBy: 1)
    let item= Int(fracPart >= 0.5 ? ceil(index) : floor(index))

    let indexPath = IndexPath(item: item, section: 0)
    collectionView.scrollToItem(at: indexPath, at: .left, animated: true)
}

1

또한 스크롤을 처리하기 위해 가짜 스크롤보기를 만들 수 있습니다.

수평 또는 수직

// === Defaults ===
let bannerSize = CGSize(width: 280, height: 170)
let pageWidth: CGFloat = 290 // ^ + paging
let insetLeft: CGFloat = 20
let insetRight: CGFloat = 20
// ================

var pageScrollView: UIScrollView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Create fake scrollview to properly handle paging
    pageScrollView = UIScrollView(frame: CGRect(origin: .zero, size: CGSize(width: pageWidth, height: 100)))
    pageScrollView.isPagingEnabled = true
    pageScrollView.alwaysBounceHorizontal = true
    pageScrollView.showsVerticalScrollIndicator = false
    pageScrollView.showsHorizontalScrollIndicator = false
    pageScrollView.delegate = self
    pageScrollView.isHidden = true
    view.insertSubview(pageScrollView, belowSubview: collectionView)

    // Set desired gesture recognizers to the collection view
    for gr in pageScrollView.gestureRecognizers! {
        collectionView.addGestureRecognizer(gr)
    }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == pageScrollView {
        // Return scrolling back to the collection view
        collectionView.contentOffset.x = pageScrollView.contentOffset.x
    }
}

func refreshData() {
    ...

    refreshScroll()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    refreshScroll()
}

/// Refresh fake scrolling view content size if content changes
func refreshScroll() {
    let w = collectionView.width - bannerSize.width - insetLeft - insetRight
    pageScrollView.contentSize = CGSize(width: pageWidth * CGFloat(banners.count) - w, height: 100)
}

0

좋아, 대신 섹션별로 스크롤하여 가변 너비 페이지 크기를 원했기 때문에 제안 된 답변이 저에게 효과적이지 않았습니다.

나는 이것을했다 (세로로만) :

   var pagesSizes = [CGSize]()
   func scrollViewDidScroll(_ scrollView: UIScrollView) {
        defer {
            lastOffsetY = scrollView.contentOffset.y
        }
        if collectionView.isDecelerating {
            var currentPage = 0
            var currentPageBottom = CGFloat(0)
            for pagesSize in pagesSizes {
                currentPageBottom += pagesSize.height
                if currentPageBottom > collectionView!.contentOffset.y {
                    break
                }
                currentPage += 1
            }
            if collectionView.contentOffset.y > currentPageBottom - pagesSizes[currentPage].height, collectionView.contentOffset.y + collectionView.frame.height < currentPageBottom {
                return // 100% of view within bounds
            }
            if lastOffsetY < collectionView.contentOffset.y {
                if currentPage + 1 != pagesSizes.count {
                    collectionView.setContentOffset(CGPoint(x: 0, y: currentPageBottom), animated: true)
                }
            } else {
                collectionView.setContentOffset(CGPoint(x: 0, y: currentPageBottom - pagesSizes[currentPage].height), animated: true)
            }
        }
    }

이 경우 섹션 높이 + 머리글 + 바닥 글을 사용하여 미리 각 페이지 크기를 계산하여 배열에 저장합니다. 그게 pagesSizes멤버


0

이것이 제 솔루션입니다. Swift 4.2에서 도움이 되었으면합니다.

class SomeViewController: UIViewController {

  private lazy var flowLayout: UICollectionViewFlowLayout = {
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: /* width */, height: /* height */)
    layout.minimumLineSpacing = // margin
    layout.minimumInteritemSpacing = 0.0
    layout.sectionInset = UIEdgeInsets(top: 0.0, left: /* margin */, bottom: 0.0, right: /* margin */)
    layout.scrollDirection = .horizontal
    return layout
  }()

  private lazy var collectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.dataSource = self
    collectionView.delegate = self
    // collectionView.register(SomeCell.self)
    return collectionView
  }()

  private var currentIndex: Int = 0
}

// MARK: - UIScrollViewDelegate

extension SomeViewController {
  func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    guard scrollView == collectionView else { return }

    let pageWidth = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
    currentIndex = Int(scrollView.contentOffset.x / pageWidth)
  }

  func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    guard scrollView == collectionView else { return }

    let pageWidth = flowLayout.itemSize.width + flowLayout.minimumLineSpacing
    var targetIndex = Int(roundf(Float(targetContentOffset.pointee.x / pageWidth)))
    if targetIndex > currentIndex {
      targetIndex = currentIndex + 1
    } else if targetIndex < currentIndex {
      targetIndex = currentIndex - 1
    }
    let count = collectionView.numberOfItems(inSection: 0)
    targetIndex = max(min(targetIndex, count - 1), 0)
    print("targetIndex: \(targetIndex)")

    targetContentOffset.pointee = scrollView.contentOffset
    var offsetX: CGFloat = 0.0
    if targetIndex < count - 1 {
      offsetX = pageWidth * CGFloat(targetIndex)
    } else {
      offsetX = scrollView.contentSize.width - scrollView.width
    }
    collectionView.setContentOffset(CGPoint(x: offsetX, y: 0.0), animated: true)
  }
}

0
final class PagingFlowLayout: UICollectionViewFlowLayout {
    private var currentIndex = 0

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        let count = collectionView!.numberOfItems(inSection: 0)
        let currentAttribute = layoutAttributesForItem(
            at: IndexPath(item: currentIndex, section: 0)
            ) ?? UICollectionViewLayoutAttributes()

        let direction = proposedContentOffset.x > currentAttribute.frame.minX
        if collectionView!.contentOffset.x + collectionView!.bounds.width < collectionView!.contentSize.width || currentIndex < count - 1 {
            currentIndex += direction ? 1 : -1
            currentIndex = max(min(currentIndex, count - 1), 0)
        }

        let indexPath = IndexPath(item: currentIndex, section: 0)
        let closestAttribute = layoutAttributesForItem(at: indexPath) ?? UICollectionViewLayoutAttributes()

        let centerOffset = collectionView!.bounds.size.width / 2
        return CGPoint(x: closestAttribute.center.x - centerOffset, y: 0)
    }
}

답변을 복사 / 붙여 넣기해서는 안됩니다. 해당하는 경우 중복으로 표시하십시오.
DonMag

0

Олень Безрогий의 원래 답변에 문제가 있었기 때문에 마지막 셀 컬렉션보기에서 처음으로 스크롤되었습니다.

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
    var indexes = yourCollectionView.indexPathsForVisibleItems
    indexes.sort()
    var index = indexes.first!
    // if velocity.x > 0 && (Get the number of items from your data) > index.row + 1 {
    if velocity.x > 0 && yourCollectionView.numberOfItems(inSection: 0) > index.row + 1 {
       index.row += 1
    } else if velocity.x == 0 {
        let cell = yourCollectionView.cellForItem(at: index)!
        let position = yourCollectionView.contentOffset.x - cell.frame.origin.x
        if position > cell.frame.size.width / 2 {
           index.row += 1
        }
    }
    
    yourCollectionView.scrollToItem(at: index, at: .centeredHorizontally, animated: true )
}

-1

를 사용 UICollectionViewFlowLayout하여 재정의하는 방법은 다음과 같습니다 targetContentOffset.

(결국에는 이것을 사용하지 않고 대신 UIPageViewController를 사용합니다.)

/**
 A UICollectionViewFlowLayout with...
 - paged horizontal scrolling
 - itemSize is the same as the collectionView bounds.size
 */
class PagedFlowLayout: UICollectionViewFlowLayout {

  override init() {
    super.init()
    self.scrollDirection = .horizontal
    self.minimumLineSpacing = 8 // line spacing is the horizontal spacing in horizontal scrollDirection
    self.minimumInteritemSpacing = 0
    if #available(iOS 11.0, *) {
      self.sectionInsetReference = .fromSafeArea // for iPhone X
    }
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("not implemented")
  }

  // Note: Setting `minimumInteritemSpacing` here will be too late. Don't do it here.
  override func prepare() {
    super.prepare()
    guard let collectionView = collectionView else { return }
    collectionView.decelerationRate = UIScrollViewDecelerationRateFast // mostly you want it fast!

    let insetedBounds = UIEdgeInsetsInsetRect(collectionView.bounds, self.sectionInset)
    self.itemSize = insetedBounds.size
  }

  // Table: Possible cases of targetContentOffset calculation
  // -------------------------
  // start |          |
  // near  | velocity | end
  // page  |          | page
  // -------------------------
  //   0   | forward  |  1
  //   0   | still    |  0
  //   0   | backward |  0
  //   1   | forward  |  1
  //   1   | still    |  1
  //   1   | backward |  0
  // -------------------------
  override func targetContentOffset( //swiftlint:disable:this cyclomatic_complexity
    forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    guard let collectionView = collectionView else { return proposedContentOffset }

    let pageWidth = itemSize.width + minimumLineSpacing
    let currentPage: CGFloat = collectionView.contentOffset.x / pageWidth
    let nearestPage: CGFloat = round(currentPage)
    let isNearPreviousPage = nearestPage < currentPage

    var pageDiff: CGFloat = 0
    let velocityThreshold: CGFloat = 0.5 // can customize this threshold
    if isNearPreviousPage {
      if velocity.x > velocityThreshold {
        pageDiff = 1
      }
    } else {
      if velocity.x < -velocityThreshold {
        pageDiff = -1
      }
    }

    let x = (nearestPage + pageDiff) * pageWidth
    let cappedX = max(0, x) // cap to avoid targeting beyond content
    //print("x:", x, "velocity:", velocity)
    return CGPoint(x: cappedX, y: proposedContentOffset.y)
  }

}


-1

여기에서 다음 을 지원 하는 사용자 지정 컬렉션보기 레이아웃을 만들었습니다 .

  • 한 번에 한 셀씩 페이징
  • 스 와이프 속도에 따라 한 번에 2 개 이상의 셀 페이징
  • 수평 또는 수직 방향

다음과 같이 쉽습니다.

let layout = PagingCollectionViewLayout()

layout.itemSize = 
layout.minimumLineSpacing = 
layout.scrollDirection = 

프로젝트 에 PagingCollectionViewLayout.swift 를 추가 할 수 있습니다.

또는

pod 'PagingCollectionViewLayout'podfile에 추가

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.