UICollectionView'ü ekrana göre değil hücrelere göre sayfalama


113

UICollectionViewYatay kaydırmaya sahibim ve her zaman tüm ekranda yan yana 2 hücre var. Bir hücrenin başlangıcında durmak için kaydırmaya ihtiyacım var. Sayfalandırma etkinleştirildiğinde, koleksiyon görünümü aynı anda 2 hücre olan tüm sayfayı kaydırır ve sonra durur.

Tek bir hücrede kaydırmayı veya hücrenin kenarında durarak birden çok hücre arasında kaydırmayı etkinleştirmem gerekiyor.

Alt sınıfa UICollectionViewFlowLayoutve yöntemi uygulamaya çalıştım targetContentOffsetForProposedContentOffset, ancak şimdiye kadar yalnızca koleksiyon görünümümü kırabildim ve kaydırmayı durdurdum. Bunu başarmanın daha kolay bir yolu var mı ve nasıl yoksa UICollectionViewFlowLayoutalt sınıfın tüm yöntemlerini gerçekten uygulamaya ihtiyacım var mı? Teşekkürler.


1
senin collectionviewcell genişliği screnn genişlik ve CollectionView'ın Çağrı etkin için eşit olmalıdır olduğunu
Erhan

Ama aynı anda 2 hücre göstermem gerekiyor. Ben iPad'deyim, bu yüzden 2 hücre ekranın yarısını paylaşıyor.
Martin Koles

2
Kullanım targetContentOffsetForProposedContentOffset:withScrollingVelocity:ve sayfalama kapatmak
Wain

Denediğim şey bu. Herhangi bir yerde örnek var mı?
Martin Koles

Yanıtlar:



23

sadece yöntemi geçersiz kıl:

- (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
Bu kod parçası bana çok yardımcı oldu, yine de mevcut kaydırma yönünü kontrol etmek zorunda kaldım ve buna göre +/- 0.5 değerini uyarladım.
helkarli

1
CollectionView.pagingEnabled = true
evya

@evya Wow haklısın. isPagingEnabled benim için çalıştı.
BigSauce

@evya harika şeyler !!
Anish Kumar

PagingEnabled sizin için nasıl çalışıyor? Benimki orijinal çağrı ofsetinde bitmeden önce süper aksıyor
Ethan Zhao

17

Özel Sayfa Genişliğiyle Yatay Sayfalama (Swift 4 ve 5)

Burada sunulan birçok çözüm, doğru şekilde uygulanmış sayfalama gibi görünmeyen bazı garip davranışlarla sonuçlanır.


Ancak bu öğreticide sunulan çözümün herhangi bir sorunu yok gibi görünüyor. Mükemmel çalışan bir sayfalama algoritması gibi hissettiriyor. 5 basit adımda uygulayabilirsiniz:

  1. Aşağıdaki özelliği türünüze ekleyin: private var indexOfCellBeforeDragging = 0
  2. Şu şekilde ayarlayın collectionView delegate:collectionView.delegate = self
  3. UICollectionViewDelegateBir uzantı aracılığıyla uygunluğu ekleyin :extension YourType: UICollectionViewDelegate { }
  4. UICollectionViewDelegateUyumluluğu uygulayan uzantıya aşağıdaki yöntemi ekleyin ve için bir değer ayarlayın 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. UICollectionViewDelegateUyumluluğu uygulayan uzantıya aşağıdaki yöntemi ekleyin, için aynı değeri ayarlayın pageWidth(bu değeri merkezi bir yerde de saklayabilirsiniz) ve aşağıdakiler için bir değer ayarlayın 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)
        }
    }
    

Bu kullanarak kimse için değiştirmeniz gereken Pop back (against velocity)olmaya bölümünü: collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true). Not.centeredHorizontally
matthew.kempson

@ matthew.kempson Düzenin nasıl davranmasını istediğinize bağlıdır. Bunu kullandığım düzen .leftiçin iyiydi
fredpi

.leftBeklendiği gibi çalışmadığını buldum . Hücreyi çok geriye itmiş gibiydi @fredpi
matthew.kempson

13

Evya'nın cevabının Swift 3 versiyonu:

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)


}

Hücrenin yan tarafına tıkladığınızda, garip bir sapma var
Maor

Hey @Maor, buna hala ihtiyacın var mı bilmiyorum, ama benim durumumda koleksiyon görünümünde sayfalamayı devre dışı bırakma sorunu düzeltildi.
Fernando Mata

2
Bunu sevilen ama hızı dikkate şey ilave böylece, hızlı küçük bira ile biraz halsiz hissettim ve çok daha pürüzsüz hale getirir: if(velocity.x > 1) { mod = 0.5; } else if(velocity.x < -1) { mod = -0.5; }daha sonra ekleme + modsonra+ Double(0.5)
Captnwalker1

12

Horinzontal kaydırma için Swift 4.2'de bunu yapmanın en kolay yolu :

İlk hücreyi kullanıyorum ve visibleCellso zaman kaydırıyorum, görünür ilk hücre genişliğinin yarısından daha azını gösteriyorsa, bir sonrakine kaydırıyorum.

Koleksiyonunuzu kaydırdığınızda dikey , basitçe değiştirmek xtarafından yve widthtarafındanheight

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

Kaynak makalenin bağlantısını ekleyebilir misiniz? Büyük Cevap BTW.
Md. Ibrahim Hassan

@ Md.IbrahimHassan Makale yok, kaynak benim. Thx
Romulo BM

işe yarıyor, ancak maalesef deneyim pürüzsüz değil
Alaa Eddine Cherbib

Pürüzsüz değil ile ne demek istiyorsun? Benim için sonuç çok düzgün canlandırıldı .. Burada
sonucuma

1
Bu iyi çalışıyor
Anuj Kumar Rai

11

Dikey hücre tabanlı sayfalama için Swift 5'teki uygulamam :

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

Bazı notlar:

  • Aksaklık yapmaz
  • SAYFALAMAYI YANLIŞ OLARAK AYARLAYIN ! (aksi takdirde bu işe yaramaz)
  • Kendi hareket hızınızı kolayca ayarlamanıza olanak tanır .
  • Bunu denedikten sonra hala bir şey çalışmıyorsa itemSize, öğenin boyutuyla gerçekten eşleşip eşleşmediğini kontrol edin, çünkü bu genellikle bir sorundur, özellikle kullanırken collectionView(_:layout:sizeForItemAt:), bunun yerine itemSize ile özel bir değişken kullanın.
  • Bu, ayarladığınızda en iyi şekilde çalışır self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

İşte yatay bir versiyon (tam olarak test etmedik, bu yüzden lütfen hataları affedin):

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

Bu kod, kişisel projemde kullandığım koda dayanmaktadır, buradan indirip Örnek hedefi çalıştırarak kontrol edebilirsiniz .


1
Swift 5 için: .fastyerine kullanınUIScollViewDecelerationRateFast
José

Bunu belirttiğiniz için teşekkürler! Bu yanıtı güncellemeyi unuttum ve şimdi yaptım!
JoniVR

Merhaba, @JoniVR, kaydırmanın dikey olarak nasıl çalışacağını göstermek için çok güzel bir açıklama örneği. Bu çalışmayı yatay bir yönde kusursuz bir şekilde gerçekleştirmek için hangi genel kod değişikliklerinin gerekli olduğunu önermeniz çok nazik olacaktır. Hedef içerik ofseti işlevinde yatay kaydırma için önerdiğiniz yukarıdaki kodun dışında. Senaryoyu tam olarak yatay olarak kopyalamak için yapılacak birçok değişiklik olduğunu düşünüyorum. Eğer Yanlışsam beni düzelt.
Shiv Prakash

9

Kısmen StevenOjo'nun cevabına dayanıyor. Bunu yatay kaydırma kullanarak ve Bounce UICollectionView kullanmadan test ettim. cellSize, CollectionViewCell boyutudur. Kaydırma hassasiyetini değiştirmek için faktörde ince ayar yapabilirsiniz.

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

7

Yaklaşım 1: Koleksiyon Görünümü

flowLayoutolduğu UICollectionViewFlowLayoutmülkiyet

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

Yaklaşım 2: Sayfa Görüntüleme Denetleyicisi

Sen kullanabilirsiniz UIPageViewControllersizin koşullarını yerine getirmesi durumunda, her sayfa ayrı görünümü denetleyicisi olurdu.


Bunun için sayfalamayı devre dışı bırakmalı ve kaydırmayı yalnızca koleksiyon görünümünde etkinleştirmeliyim?
nr5

Bu, en son swift4 / Xcode9.3 için çalışmıyor, targetContentOffset'in bir bellek alanı yok. Kaydırmayı uyguladım, ancak "hafifçe vurduğunuzda" hücre konumunu ayarlamıyor.
Steven B.

Birkaç hücre ile çalışıyor ancak 13. hücreye geldiğimde önceki hücreye geri dönmeye başlıyor ve devam edemezsiniz.
Christopher Smit

4

Bu, bunu yapmanın doğrudan bir yoludur.

Durum basit, ancak son olarak oldukça yaygındır (sabit hücre boyutu ve hücreler arasında sabit boşluk olan tipik küçük resim kaydırıcısı)

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
}

Bir scrollToOffset'i çağırmak veya düzenlere dalmak için bir neden olmadığını unutmayın. Yerel kaydırma davranışı zaten her şeyi yapıyor.

Şerefe :)


2
İsteğe bağlı collectionView.decelerationRate = .fastolarak daha yakın mimik varsayılan sayfalamayı ayarlayabilirsiniz .
elfanek

1
Bu gerçekten güzel. @elfanek Küçük ve nazik bir kaydırma yapmadığınız sürece işe yarayan ayarı buldum, sonra hızlıca titriyor gibi görünüyor.
mylogon

3

Evya'nın cevabı gibi, ama biraz daha yumuşak çünkü targetContentOffset'i sıfıra ayarlamıyor.

- (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

Hız dinleme için Romulo BM cevabını değiştirin

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

Bunu UICollectionView alt sınıflamadan yapmanın bir yolunu buldum, sadece contentOffset'i yatay olarak hesaplayarak. Açıkçası isPagingEnabled olmadan true ayarlayın. İşte kod:

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

İşte Swift 3'teki versiyonum. Kaydırma bittikten sonra ofseti hesaplayın ve ofseti animasyonla ayarlayın.

collectionLayout bir 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

Ayrıca kaydırmayı işlemek için sahte kaydırma görünümü oluşturabilirsiniz.

Yatay veya Dikey

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

Tamam, bu yüzden önerilen yanıtlar benim için işe yaramadı çünkü bunun yerine bölümlere göre kaydırmak ve dolayısıyla değişken genişlikte sayfa boyutlarına sahip olmak istedim

Bunu yaptım (yalnızca dikey):

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

Bu durumda, her bir sayfa boyutunu önceden bölüm yüksekliği + üstbilgi + altbilgiyi kullanarak hesaplıyorum ve dizide saklıyorum. Bu pagesSizesüye


0

Swift 4.2'de benim çözümüm bu, keşke sana yardımcı olabilseydi.

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

Cevapları kopyalayıp yapıştırmamalısınız. Uygunsa, yinelenen olarak işaretleyin.
DonMag

0

Олень Безрогий'nin orijinal cevabında bir sorun vardı, bu nedenle son hücre koleksiyonu görünümü başa kaydırılıyordu

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

Bunu UICollectionViewFlowLayout, geçersiz kılmak için a kullanarak yapmanın yolu targetContentOffset:

(Her ne kadar sonunda bunu kullanmıyorum ve bunun yerine UIPageViewController'ı kullanıyorum.)

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

Burada aşağıdakileri destekleyen özel bir koleksiyon görünümü düzeni oluşturdum :

  • her seferinde bir hücreyi çağırmak
  • kaydırma hızına bağlı olarak bir seferde 2'den fazla hücreyi sayfalama
  • yatay veya dikey yönler

bu kadar kolay:

let layout = PagingCollectionViewLayout()

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

PagingCollectionViewLayout.swift'i projenize ekleyebilirsiniz

veya

pod 'PagingCollectionViewLayout'pod dosyanıza ekleyin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.