Paging UICollectionView berdasarkan sel, bukan layar

113

saya sudah UICollectionView pengguliran horizontal dan selalu ada 2 sel berdampingan di seluruh layar. Saya membutuhkan pengguliran untuk berhenti di awal sel. Dengan paging diaktifkan, tampilan koleksi menggulir seluruh halaman, yaitu 2 sel sekaligus, lalu berhenti.

Saya perlu mengaktifkan pengguliran dengan satu sel, atau menggulir dengan beberapa sel dengan berhenti di tepi sel.

Saya mencoba membuat subkelas UICollectionViewFlowLayoutdan menerapkan metode tersebut targetContentOffsetForProposedContentOffset, tetapi sejauh ini saya hanya dapat merusak tampilan koleksi saya dan berhenti bergulir. Apakah ada cara yang lebih mudah untuk mencapai ini dan bagaimana, atau apakah saya benar-benar perlu menerapkan semua metode UICollectionViewFlowLayoutsubclass? Terima kasih.

Martin Koles
sumber
1
lebar collectionviewcell Anda harus sama dengan lebar screnn dan collectionView Paging diaktifkan
Erhan
Tapi saya perlu menunjukkan 2 sel sekaligus. Saya menggunakan iPad, jadi masing-masing 2 sel berbagi setengah layar.
Martin Koles
2
Gunakan targetContentOffsetForProposedContentOffset:withScrollingVelocity:dan matikan paging
Wain
Inilah yang saya coba. Ada contoh di suatu tempat?
Martin Koles

Jawaban:

23

ganti saja metode ini:

- (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];
}
evya
sumber
1
Sepotong kode itu sangat membantu saya, saya harus menambahkan cek untuk arah pengguliran saat ini dan karenanya menyesuaikan nilai +/- 0,5.
helkarli
1
Anda dapat menyetel collectionView.pagingEnabled = true
evya
@evya Wow kamu benar. isPagingEnabled berfungsi untuk saya.
BigSauce
@evya barang bagus !!
Anish Kumar
Bagaimana pagingEnabled bekerja untuk kalian? Milik saya menjadi super glitchy sebelum berakhir di offset paging asli
Ethan Zhao
17

Halaman Horizontal Dengan Lebar Halaman Kustom (Swift 4 & 5)

Banyak solusi yang disajikan di sini menghasilkan beberapa perilaku aneh yang tidak terasa seperti paging yang diterapkan dengan benar.


Solusi yang disajikan dalam tutorial ini , bagaimanapun, tampaknya tidak memiliki masalah apa pun. Ini hanya terasa seperti algoritma paging yang bekerja dengan sempurna. Anda dapat menerapkannya dalam 5 langkah sederhana:

  1. Tambahkan properti berikut ke tipe Anda: private var indexOfCellBeforeDragging = 0
  2. Atur collectionView delegateseperti ini:collectionView.delegate = self
  3. Tambahkan kesesuaian UICollectionViewDelegatemelalui ekstensi:extension YourType: UICollectionViewDelegate { }
  4. Tambahkan metode berikut ke ekstensi yang menerapkan UICollectionViewDelegatekesesuaian dan tetapkan nilai untuk 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. Tambahkan metode berikut ke ekstensi yang menerapkan UICollectionViewDelegatekesesuaian, tetapkan nilai yang sama untuk pageWidth(Anda juga dapat menyimpan nilai ini di tempat sentral) dan menetapkan nilai untuk 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)
        }
    }
fredpi
sumber
Bagi siapa pun menggunakan ini Anda perlu mengubah Pop back (against velocity)bagian menjadi: collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true). Perhatikan.centeredHorizontally
matthew.kempson
@ matthew.kempson Bergantung pada bagaimana Anda ingin tata letak berperilaku. Untuk tata letak yang saya gunakan ini, .leftbaik
fredpi
Saya menemukan itu .lefttidak berfungsi seperti yang diharapkan. Tampaknya mendorong sel terlalu jauh ke belakang @fredpi
matthew.kempson
13

Versi Swift 3 dari jawaban 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)


}
StevenOjo
sumber
Ketika Anda mengklik di sisi sel, ada offset aneh
Maor
Hai @ Maor, saya tidak tahu apakah Anda masih membutuhkannya, tetapi dalam kasus saya itu telah diperbaiki dengan menonaktifkan paging pada tampilan koleksi.
Fernando Mata
2
Saya menyukai ini tetapi merasa sedikit lamban dengan gesekan kecil yang cepat, jadi saya menambahkan sesuatu untuk memperhitungkan kecepatan dan membuatnya jauh lebih mulus: if(velocity.x > 1) { mod = 0.5; } else if(velocity.x < -1) { mod = -0.5; }lalu tambahkan + modsetelah+ Double(0.5)
Captnwalker1
12

Inilah cara termudah yang saya temukan untuk melakukannya di Swift 4.2 untuk gulir horinzontal :

Saya menggunakan sel pertama visibleCellsdan menggulir ke sana, jika sel pertama yang terlihat menunjukkan kurang dari setengah lebarnya, saya menggulir ke sel berikutnya.

Jika koleksi Anda bergulir secara vertikal , cukup ubah xoleh ydan widtholehheight

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 )
}
Romulo BM
sumber
Bisakah Anda menambahkan tautan ke artikel sumber. Jawaban Hebat BTW.
Md. Ibrahim Hassan
@ Md.IbrahimHassan Tidak ada artikel, saya sumbernya. Thx
Romulo BM
itu berhasil, tapi sayangnya pengalamannya tidak mulus
Alaa Eddine Cherbib
Apa maksudmu dengan tidak mulus? Bagi saya, hasil animasi sangat halus .. Lihat hasil saya di sini
Romulo BM
1
Ini berfungsi dengan baik
Anuj Kumar Rai
9

Sebagian berdasarkan jawaban StevenOjo. Saya telah menguji ini menggunakan scrolling horizontal dan tanpa Bounce UICollectionView. cellSize adalah ukuran CollectionViewCell. Anda dapat mengubah faktor untuk mengubah sensitivitas pengguliran.

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)
}
John Cido
sumber
9

Inilah implementasi saya di Swift 5 untuk paging berbasis sel vertikal :

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

Beberapa catatan:

  • Tidak ada kesalahan
  • ATUR HALAMAN KE SALAH ! (jika tidak ini tidak akan berhasil)
  • Memungkinkan Anda mengatur flickvelocity Anda sendiri dengan mudah.
  • Jika ada sesuatu yang masih tidak berfungsi setelah mencoba ini, periksa apakah Anda itemSizebenar - benar cocok dengan ukuran item karena itu sering menjadi masalah, terutama saat menggunakan collectionView(_:layout:sizeForItemAt:), gunakan variabel khusus dengan itemSize sebagai gantinya.
  • Ini bekerja paling baik saat Anda menyetel self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

Ini adalah versi horizontal (belum mengujinya secara menyeluruh jadi mohon maafkan jika ada kesalahan):

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

Kode ini didasarkan pada kode yang saya gunakan dalam proyek pribadi saya, Anda dapat memeriksanya di sini dengan mengunduhnya dan menjalankan target Contoh.

JoniVR
sumber
1
Untuk Swift 5: gunakan .fastsebagai gantiUIScollViewDecelerationRateFast
José
Terima kasih telah menunjukkannya! Lupa memperbarui jawaban ini dan baru saja melakukannya!
JoniVR
Hai, @JoniVR contoh penjelasan yang sangat bagus untuk menunjukkan bagaimana gesekan akan bekerja secara vertikal. Anda akan sangat baik jika menyarankan perubahan kode keseluruhan apa yang diperlukan untuk membuat ini bekerja dengan sempurna dalam arah horizontal. Terlepas dari kode di atas Anda menyarankan untuk menggesek horizontal dalam fungsi offset konten target. Saya pikir ada banyak perubahan yang harus dilakukan untuk mereplikasi skenario yang tepat secara horizontal. Koreksi saya jika saya salah.
Shiv Prakash
7

Pendekatan 1: Tampilan Koleksi

flowLayoutadalah UICollectionViewFlowLayoutproperti

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

Pendekatan 2: Pengontrol Tampilan Halaman

Anda dapat menggunakan UIPageViewControllerjika memenuhi persyaratan Anda, setiap halaman akan memiliki pengontrol tampilan terpisah.

pengguna1046037
sumber
Untuk ini saya harus menonaktifkan paging dan mengaktifkan scrolling hanya di collectionview?
nr5
Ini tidak berfungsi untuk swift4 / Xcode9.3 terbaru, targetContentOffset tidak memiliki bidang memori. Saya menerapkan pengguliran tetapi tidak menyesuaikan lokasi sel saat Anda "menjentik".
Steven B.
Bekerja dengan beberapa sel tetapi ketika saya sampai ke sel 13 itu mulai melompat kembali ke sel sebelumnya dan Anda tidak dapat melanjutkan.
Christopher Smit
4

Ini adalah cara langsung untuk melakukan ini.

Kasingnya sederhana, tetapi akhirnya cukup umum (penggulung thumbnail khas dengan ukuran sel tetap dan celah tetap antar sel)

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
}

Perhatikan bahwa tidak ada alasan untuk memanggil scrollToOffset atau menyelami tata letak. Perilaku pengguliran asli sudah melakukan segalanya.

Salam Semua :)

Moose
sumber
2
Secara opsional, Anda dapat menyetel collectionView.decelerationRate = .fastagar lebih dekat paging default mimmic.
elfanek
1
Ini sangat bagus. @elfanek Saya telah menemukan bahwa pengaturan yang berfungsi baik kecuali Anda melakukan gesekan kecil & lembut maka tampaknya berkedip dengan cepat.
mylogon
3

Mirip seperti jawaban evya, tapi sedikit lebih halus karena tidak menyetel targetContentOffset ke nol.

- (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);
        }
    }
}
skensell
sumber
3

modifikasi jawaban Romulo BM untuk mendengarkan kecepatan

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 )
}
Олень Безрогий
sumber
2

Cepat 5

Saya telah menemukan cara untuk melakukan ini tanpa subclass UICollectionView, hanya menghitung contentOffset secara horizontal. Jelas tanpa isPagingEnabled, set true. Ini kodenya:

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
}
carlosobedgomez.dll
sumber
1

Ini adalah versi saya di Swift 3. Hitung offset setelah pengguliran berakhir dan sesuaikan offset dengan animasi.

collectionLayout adalah 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)
}
Misha Kouznetsov
sumber
1

Anda juga dapat membuat tampilan gulir palsu untuk menangani pengguliran.

Horizontal atau Vertikal

// === 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)
}
icompot.dll
sumber
0

Oke jadi jawaban yang diusulkan tidak berhasil untuk saya karena saya ingin menggulir berdasarkan bagian, dan dengan demikian, memiliki ukuran halaman lebar variabel

Saya melakukan ini (hanya vertikal):

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

Dalam kasus ini, saya menghitung setiap ukuran halaman sebelumnya menggunakan tinggi bagian + header + footer, dan menyimpannya dalam array. Itu pagesSizesanggotanya

Antzi
sumber
0

Ini adalah solusi saya, di Swift 4.2, saya berharap ini dapat membantu Anda.

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)
  }
}
Leo
sumber
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)
    }
}
Mecid
sumber
Anda tidak boleh menyalin / menempel jawaban. Tandai sebagai duplikat jika sesuai.
DonMag
0

Jawaban asli Олень Безрогий bermasalah, jadi tampilan koleksi sel terakhir digulir ke awal

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 )
}
Arthur Avagyan
sumber
-1

Inilah cara saya melakukannya dengan menggunakan UICollectionViewFlowLayoutto override targetContentOffset:

(Meskipun pada akhirnya, saya akhirnya tidak menggunakan ini dan menggunakan UIPageViewController sebagai gantinya.)

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

}
Hlung
sumber
-1

saya membuat tata letak tampilan koleksi kustom di sini yang mendukung:

  • paging satu sel pada satu waktu
  • paging 2+ sel sekaligus tergantung pada kecepatan gesek
  • arah horizontal atau vertikal

semudah:

let layout = PagingCollectionViewLayout()

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

Anda tinggal menambahkan PagingCollectionViewLayout.swift ke proyek Anda

atau

tambahkan pod 'PagingCollectionViewLayout'ke podfile Anda

ak.
sumber