Phân trang UICollectionXem theo ô, không phải màn hình


113

Tôi có UICollectionViewvới tính năng cuộn ngang và luôn có 2 ô cạnh nhau trên toàn bộ màn hình. Tôi cần quá trình cuộn dừng lại ở đầu ô. Khi bật phân trang, chế độ xem bộ sưu tập sẽ cuộn toàn bộ trang, là 2 ô cùng một lúc, sau đó dừng lại.

Tôi cần bật tính năng cuộn theo một ô hoặc cuộn theo nhiều ô với việc dừng lại ở cạnh ô.

Tôi đã cố gắng phân lớp con UICollectionViewFlowLayoutvà triển khai phương thức targetContentOffsetForProposedContentOffset, nhưng cho đến nay tôi chỉ có thể phá vỡ chế độ xem bộ sưu tập của mình và nó ngừng cuộn. Có cách nào dễ dàng hơn để đạt được điều này không và bằng cách nào, hoặc tôi thực sự cần phải triển khai tất cả các phương thức của UICollectionViewFlowLayoutlớp con? Cảm ơn.


1
chiều rộng collectionviewcell của bạn phải bằng chiều rộng screnn và đã bật collectionView Paging
Erhan

Nhưng tôi cần hiển thị 2 ô cùng một lúc. Tôi đang sử dụng iPad, vì vậy 2 ô chia sẻ một nửa màn hình mỗi ô.
Martin Koles

2
Sử dụng targetContentOffsetForProposedContentOffset:withScrollingVelocity:và tắt phân trang
Wain

Đây là những gì tôi đang cố gắng. Bất kỳ ví dụ ở đâu đó?
Martin Koles

Câu trả lời:



23

chỉ cần ghi đè phương thức:

- (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
Đoạn mã đó đã giúp tôi rất nhiều, mặc dù vậy, tôi đã phải thêm kiểm tra hướng cuộn hiện tại và phù hợp với giá trị +/- 0,5.
helkarli

1
Bạn có thể đặt collectionView.pagingEnabled = true
evya

@evya Wow bạn nói đúng. isPagingEnabled đã làm việc cho tôi.
BigSauce

@evya thứ tuyệt vời !!
Anish Kumar

Làm thế nào để pagingEnabled hoạt động cho các bạn? Mine được siêu glitchy trước khi dừng lại ở phân trang gốc bù đắp
Ethan Zhao

17

Phân trang ngang với chiều rộng trang tùy chỉnh (Swift 4 & 5)

Nhiều giải pháp được trình bày ở đây dẫn đến một số hành vi kỳ lạ không giống như phân trang được triển khai đúng cách.


Tuy nhiên, giải pháp được trình bày trong hướng dẫn này dường như không có bất kỳ vấn đề nào. Nó giống như một thuật toán phân trang hoạt động hoàn hảo. Bạn có thể thực hiện nó trong 5 bước đơn giản:

  1. Thêm thuộc tính sau vào loại của bạn: private var indexOfCellBeforeDragging = 0
  2. Đặt collectionView delegatenhư thế này:collectionView.delegate = self
  3. Thêm sự phù hợp UICollectionViewDelegatequa một tiện ích mở rộng:extension YourType: UICollectionViewDelegate { }
  4. Thêm phương thức sau vào tiện ích mở rộng triển khai UICollectionViewDelegatetuân thủ và đặt giá trị cho 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. Thêm phương thức sau vào tiện ích triển khai UICollectionViewDelegatetuân thủ, đặt cùng một giá trị cho pageWidth(bạn cũng có thể lưu giá trị này ở vị trí trung tâm) và đặt giá trị cho 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)
        }
    }

Đối với bất cứ ai sử dụng này, bạn cần phải thay đổi Pop back (against velocity)một phần là: collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true). Lưu ý.centeredHorizontally
matthew.kempson

@ matthew.kempson Tùy thuộc vào cách bạn muốn bố cục hoạt động. Đối với bố cục mà tôi đã sử dụng với cái này, .leftvẫn ổn
fredpi

Tôi thấy điều đó .leftkhông hoạt động như mong đợi. Dường như để thúc đẩy các tế bào quá xa trở lại @fredpi
matthew.kempson

13

Phiên bản Swift 3 của câu trả lời của Evya:

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)


}

Khi bạn nhấp vào cạnh của ô, có một khoảng chênh lệch kỳ lạ
Maor

Xin chào @Maor, tôi không biết bạn có còn cần nó không, nhưng trong trường hợp của tôi, điều đó đã được khắc phục khi tắt tính năng phân trang trên chế độ xem bộ sưu tập.
Fernando Mata

2
Tôi yêu này nhưng cảm thấy một chút chậm chạp với swipes nhỏ nhanh chóng, vì vậy tôi thêm một cái gì đó để có vận tốc vào tài khoản và làm cho nó trơn tru hơn nhiều: if(velocity.x > 1) { mod = 0.5; } else if(velocity.x < -1) { mod = -0.5; }sau đó thêm + modsau+ Double(0.5)
Captnwalker1

12

Đây là cách dễ nhất mà tôi tìm thấy để làm điều đó trong Swift 4.2 cho horinzontal cuộn:

Tôi đang sử dụng ô đầu tiên trên visibleCellsvà cuộn đến sau đó, nếu ô hiển thị đầu tiên hiển thị ít hơn một nửa chiều rộng của nó, tôi sẽ cuộn sang ô tiếp theo.

Nếu bộ sưu tập của bạn di chuyển theo chiều dọc , chỉ cần thay đổi xbằng ywidthbởiheight

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

Bạn có thể vui lòng thêm liên kết đến nguồn bài viết. Câu trả lời tuyệt vời BTW.
Md. Ibrahim Hassan

@ Md.IbrahimHassan Không có bài viết nào, tôi là nguồn. Thx
Romulo BM

nó hoạt động, nhưng tiếc là kinh nghiệm không phải là mịn
Alaa Eddine Cherbib

Ý bạn là gì với không suôn sẻ? Đối với tôi, kết quả là hoạt hình rất mịn .. Xem kết quả của tôi ở đây
Romulo BM

1
Điều này hoạt động tốt
Anuj Kumar Rai

9

Một phần dựa trên câu trả lời của StevenOjo. Tôi đã thử nghiệm điều này bằng cách sử dụng cuộn ngang và không có Bounce UICollectionView. cellSize là kích thước CollectionViewCell. Bạn có thể điều chỉnh hệ số để sửa đổi độ nhạy cuộn.

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

Đây là cách triển khai của tôi trong Swift 5 cho phân trang dựa trên ô dọc :

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

Một số lưu ý:

  • Không trục trặc
  • ĐẶT PAGING THÀNH FALSE ! (nếu không điều này sẽ không hoạt động)
  • Cho phép bạn thiết lập vận tốc của riêng mình một cách dễ dàng.
  • Nếu điều gì đó vẫn không hoạt động sau khi thử cách này, hãy kiểm tra xem bạn có itemSizethực sự khớp với kích thước của mặt hàng hay không vì đó thường là một vấn đề, đặc biệt là khi sử dụng collectionView(_:layout:sizeForItemAt:), hãy sử dụng biến tùy chỉnh với itemSize để thay thế.
  • Điều này hoạt động tốt nhất khi bạn thiết lập self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

Đây là phiên bản ngang (chưa được kiểm tra kỹ lưỡng vì vậy xin vui lòng bỏ qua cho bất kỳ sai sót nào):

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

Mã này dựa trên mã tôi sử dụng trong dự án cá nhân của mình, bạn có thể kiểm tra tại đây bằng cách tải xuống và chạy mục tiêu Ví dụ.


1
Đối với Swift 5: sử dụng .fastthay vìUIScollViewDecelerationRateFast
José

Cảm ơn vì đã chỉ ra điều đó! Quên cập nhật câu trả lời này và vừa mới làm!
JoniVR

Xin chào, @JoniVR, ví dụ giải thích rất hay để hiển thị cách vuốt sẽ hoạt động theo chiều dọc. Bạn sẽ rất tử tế khi đề xuất những thay đổi mã tổng thể nào cần thiết để làm cho công việc này hoạt động hoàn hảo theo chiều ngang. Ngoài mã ở trên, bạn đã đề xuất cho chức năng bù đắp nội dung mục tiêu vuốt ngang trong. Tôi nghĩ rằng có rất nhiều thay đổi được thực hiện để tái tạo kịch bản chính xác theo chiều ngang. Đúng nếu tôi đã sai lầm.
Shiv Prakash

7

Phương pháp 1: Chế độ xem Bộ sưu tập

flowLayoutUICollectionViewFlowLayouttài sản

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

Phương pháp 2: Bộ điều khiển Lượt xem Trang

Bạn có thể sử dụng UIPageViewControllernếu nó đáp ứng yêu cầu của bạn, mỗi trang sẽ có một bộ điều khiển chế độ xem riêng biệt.


Đối với điều này, tôi phải tắt phân trang và chỉ bật cuộn trong chế độ xem bộ sưu tập?
nr5,

Điều này không hoạt động đối với swift4 / Xcode9.3 mới nhất, targetContentOffset không có trường bộ nhớ. Tôi đã triển khai thao tác cuộn nhưng nó không điều chỉnh vị trí ô khi bạn "vuốt".
Steven B.

Hoạt động với một vài ô nhưng khi tôi đến ô 13, nó bắt đầu nhảy trở lại ô trước đó và bạn không thể tiếp tục.
Christopher Smit,

4

Đây là một cách đơn giản để làm điều này.

Trường hợp này đơn giản, nhưng cuối cùng khá phổ biến (cuộn hình thu nhỏ điển hình với kích thước ô cố định và khoảng cách cố định giữa các ô)

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
}

Lưu ý rằng không có lý do gì để gọi một scrollToOffset hoặc đi sâu vào các bố cục. Hành vi cuộn gốc đã thực hiện mọi thứ.

Chúc mừng tất cả :)


2
Bạn có thể tùy chọn đặt thành collectionView.decelerationRate = .fastphân trang mặc định mimmic gần hơn.
elfanek

1
Điều này thực sự tốt đẹp. @elfanek Tôi thấy cài đặt đó hoạt động tốt trừ khi bạn thực hiện một thao tác vuốt nhẹ và nhỏ thì nó chỉ nhấp nháy nhanh chóng.
mylogon

3

Giống như câu trả lời của evya, nhưng mượt mà hơn một chút vì nó không đặt targetContentOffset bằng 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

sửa đổi câu trả lời Romulo BM để nghe vận tốc

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

Swift 5

Tôi đã tìm ra cách để thực hiện việc này mà không cần phân lớp UICollectionView, chỉ tính toán contentOffset theo chiều ngang. Rõ ràng là không có isPagingEnabled được đặt là true. Đây là mã:

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

Đây là phiên bản của tôi về nó trong Swift 3. Tính toán độ lệch sau khi quá trình cuộn kết thúc và điều chỉnh độ lệch bằng hoạt ảnh.

collectionLayout là một 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

Ngoài ra, bạn có thể tạo chế độ xem cuộn giả để xử lý việc cuộn.

Ngang hoặc dọc

// === 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

Được rồi, vì vậy các câu trả lời được đề xuất không phù hợp với tôi vì tôi muốn cuộn theo các phần thay vào đó, có kích thước trang chiều rộng thay đổi

Tôi đã làm điều này (chỉ theo chiều dọc):

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

Trong trường hợp này, tôi tính toán trước kích thước từng trang bằng cách sử dụng chiều cao phần + đầu trang + chân trang và lưu trữ nó trong mảng. Đó là pagesSizesthành viên


0

Đây là giải pháp của tôi, trong Swift 4.2, tôi ước nó có thể giúp ích cho bạn.

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

Bạn không nên sao chép / dán câu trả lời. Đánh dấu nó là trùng lặp nếu thích hợp.
DonMag

0

Câu trả lời ban đầu của Олень Безрогий có vấn đề, vì vậy ở chế độ xem bộ sưu tập ô cuối cùng đang cuộn về đầu

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

Đây là cách của tôi để làm điều đó bằng cách sử dụng một UICollectionViewFlowLayoutđể ghi đè targetContentOffset:

(Mặc dù cuối cùng, tôi không sử dụng điều này và sử dụng UIPageViewController thay thế.)

/**
 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

tôi đã tạo bố cục dạng xem bộ sưu tập tùy chỉnh ở đây hỗ trợ:

  • phân trang từng ô một
  • phân trang 2+ ô cùng một lúc tùy thuộc vào tốc độ vuốt
  • hướng ngang hoặc dọc

nó dễ dàng như:

let layout = PagingCollectionViewLayout()

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

bạn chỉ cần thêm PagingCollectionViewLayout.swift vào dự án của mình

hoặc là

thêm pod 'PagingCollectionViewLayout'vào podfile của bạn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.