Paging UICollectionView nach Zellen, nicht nach Bildschirm


110

Ich habe UICollectionViewmit horizontalem Scrollen und es gibt immer 2 Zellen nebeneinander pro gesamten Bildschirm. Ich brauche das Scrollen, um am Anfang einer Zelle anzuhalten. Wenn Paging aktiviert ist, scrollt die Sammlungsansicht die gesamte Seite, die aus 2 Zellen gleichzeitig besteht, und stoppt dann.

Ich muss das Scrollen durch eine einzelne Zelle oder das Scrollen durch mehrere Zellen mit Anhalten am Rand der Zelle aktivieren.

Ich habe versucht, UICollectionViewFlowLayoutdie Methode in Unterklassen zu unterteilen und zu implementieren targetContentOffsetForProposedContentOffset, aber bisher konnte ich nur meine Sammlungsansicht unterbrechen und das Scrollen wurde gestoppt. Gibt es einen einfacheren Weg, dies zu erreichen und wie, oder muss ich wirklich alle Methoden der UICollectionViewFlowLayoutUnterklasse implementieren ? Vielen Dank.


1
Ihre Collectionviewcell-Breite muss der Screnn-Breite entsprechen und CollectionView Paging aktiviert sein
Erhan

Aber ich muss 2 Zellen gleichzeitig zeigen. Ich bin auf dem iPad, also teilen sich 2 Zellen jeweils eine Hälfte des Bildschirms.
Martin Koles

2
Verwenden targetContentOffsetForProposedContentOffset:withScrollingVelocity:und deaktivieren Sie Paging
Wain

Das versuche ich. Irgendwo ein Beispiel?
Martin Koles

Antworten:



23

Überschreiben Sie einfach die Methode:

- (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
Dieser Code hat mir sehr geholfen, ich musste jedoch die Prüfung für die aktuelle Bildlaufrichtung hinzufügen und den +/- 0,5-Wert entsprechend anpassen.
Helkarli

1
Sie können collectionView.pagingEnabled = true setzen
evya

@evya Wow du hast recht. isPagingEnabled hat für mich gearbeitet.
BigSauce

@evya tolles Zeug !!
Anish Kumar

Wie funktioniert pagingEnabled für euch? Meins wird super unangenehm, bevor es zum ursprünglichen Paging-Offset kommt
Ethan Zhao,

16

Horizontales Paging mit benutzerdefinierter Seitenbreite (Swift 4 & 5)

Viele der hier vorgestellten Lösungen führen zu einem seltsamen Verhalten, das sich nicht wie richtig implementiertes Paging anfühlt.


Die in diesem Tutorial vorgestellte Lösung scheint jedoch keine Probleme zu haben. Es fühlt sich einfach wie ein perfekt funktionierender Paging-Algorithmus an. Sie können es in 5 einfachen Schritten implementieren:

  1. Fügen Sie Ihrem Typ die folgende Eigenschaft hinzu: private var indexOfCellBeforeDragging = 0
  2. Stellen Sie collectionView delegateFolgendes ein:collectionView.delegate = self
  3. Konformität UICollectionViewDelegateüber eine Erweiterung hinzufügen:extension YourType: UICollectionViewDelegate { }
  4. Fügen Sie der Erweiterung, die die UICollectionViewDelegateKonformität implementiert, die folgende Methode hinzu und legen Sie einen Wert für fest 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. Fügen Sie der Erweiterung, die die UICollectionViewDelegateKonformität implementiert , die folgende Methode hinzu , legen Sie denselben Wert für fest pageWidth(Sie können diesen Wert auch an einer zentralen Stelle speichern) und legen Sie einen Wert für fest 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)
        }
    }
    

Für jeden, der dies verwendet, müssen Sie das Pop back (against velocity)Teil so ändern , dass es : collectionViewLayout.collectionView!.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true). Beachten Sie die.centeredHorizontally
matthew.kempson

@ matthew.kempson Hängt davon ab, wie sich das Layout verhalten soll. Für das Layout, mit dem ich dies verwendet habe, .leftwar fein
Fredpi

Ich fand, dass .leftdas nicht wie erwartet funktionierte. Es schien die Zelle zu weit zurück zu schieben @fredpi
matthew.kempson

13

Swift 3-Version von Evyas Antwort:

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)


}

Wenn Sie auf die Seite der Zelle klicken, gibt es einen seltsamen Versatz
Maor

Hey @Maor, ich weiß nicht, ob Sie es noch brauchen, aber in meinem Fall wurde das Deaktivieren des Paging in der Sammlungsansicht behoben.
Fernando Mata

2
Ich liebte das, fühlte mich aber ein wenig träge mit schnellen kleinen Wischbewegungen, also fügte ich etwas hinzu, um die Geschwindigkeit zu berücksichtigen und es viel glatter zu machen: if(velocity.x > 1) { mod = 0.5; } else if(velocity.x < -1) { mod = -0.5; }dann füge + modnach dem+ Double(0.5)
Captnwalker1

12

Hier ist der einfachste Weg, den ich in Swift 4.2 für horinzontale Schriftrollen gefunden habe:

Ich verwende die erste Zelle visibleCellsund scrolle zu dann. Wenn die erste sichtbare Zelle weniger als die Hälfte ihrer Breite zeigt, scrolle ich zur nächsten.

Wenn Ihre Sammlung vertikal scrollt , ändern Sie sie einfach xnach yund widthnachheight

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

Können Sie bitte den Link zum Quellartikel hinzufügen? Tolle Antwort übrigens.
Md. Ibrahim Hassan

@ Md.IbrahimHassan Es gibt keinen Artikel, ich bin die Quelle. Thx
Romulo BM

es funktioniert, aber leider ist die Erfahrung nicht glatt
Alaa Eddine Cherbib

Was meinst du mit nicht glatt? Für mich ist das Ergebnis sehr flüssig animiert. Siehe mein Ergebnis hier
Romulo BM

9

Teilweise basierend auf StevenOjos Antwort. Ich habe dies mit einem horizontalen Bildlauf und ohne Bounce UICollectionView getestet. cellSize hat die CollectionViewCell-Größe. Sie können den Faktor anpassen, um die Bildlaufempfindlichkeit zu ändern.

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

Hier ist meine Implementierung in Swift 5 für vertikales zellbasiertes Paging:

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

Einige Notizen:

  • Passt nicht
  • SETZEN SIE PAGING AUF FALSCH ! (sonst funktioniert das nicht)
  • Ermöglicht das einfache Einstellen Ihrer eigenen Flickvelocity .
  • Wenn nach dem Versuch immer noch etwas nicht funktioniert, überprüfen Sie, ob Ihre itemSizeGröße tatsächlich mit der Größe des Elements übereinstimmt, da dies häufig ein Problem darstellt, insbesondere bei Verwendung collectionView(_:layout:sizeForItemAt:). Verwenden Sie stattdessen eine benutzerdefinierte Variable mit itemSize.
  • Dies funktioniert am besten, wenn Sie einstellen self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast.

Hier ist eine horizontale Version (habe sie nicht gründlich getestet, bitte verzeihen Sie Fehler):

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

Dieser Code basiert auf dem Code, den ich in meinem persönlichen Projekt verwende. Sie können ihn hier überprüfen, indem Sie ihn herunterladen und das Beispielziel ausführen .


1
Für Swift 5: Verwenden Sie .fastanstelle vonUIScollViewDecelerationRateFast
José

Vielen Dank für den Hinweis! Ich habe vergessen, diese Antwort zu aktualisieren und habe es einfach getan!
JoniVR

Hallo, @JoniVR sehr schönes Erklärungsbeispiel, um zu zeigen, wie das Wischen vertikal funktioniert. Es ist sehr nett von Ihnen, Ihnen vorzuschlagen, welche allgemeinen Codeänderungen erforderlich sind, damit dies in horizontaler Richtung fehlerfrei funktioniert. Abgesehen von dem obigen Code, den Sie für das horizontale Wischen in der Funktion zum Versetzen von Zielinhalten vorgeschlagen haben. Ich denke, es müssen viele Änderungen vorgenommen werden, um das genaue Szenario horizontal zu replizieren. Korrigieren Sie mich, wenn ich falsch liege.
Shiv Prakash

7

Ansatz 1: Sammlungsansicht

flowLayoutist UICollectionViewFlowLayoutEigentum

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

Ansatz 2: Page View Controller

Sie können verwenden, UIPageViewControllerwenn es Ihren Anforderungen entspricht. Jede Seite verfügt über einen separaten Ansichts-Controller.


Dazu muss ich das Paging deaktivieren und das Scrollen nur in der Sammlungsansicht aktivieren?
Nr. 5

Dies funktioniert nicht mit dem neuesten swift4 / Xcode9.3. TargetContentOffset verfügt nicht über ein Speicherfeld. Ich habe das Scrollen implementiert, aber es passt die Zellenposition nicht an, wenn Sie "streichen".
Steven B.

Funktioniert mit einigen Zellen, aber wenn ich zu Zelle 13 komme, springt es zurück zur vorherigen Zelle und Sie können nicht fortfahren.
Christopher Smit

4

Dies ist ein direkter Weg, dies zu tun.

Der Fall ist einfach, aber schließlich recht häufig (typischer Miniaturbild-Scroller mit fester Zellengröße und festem Abstand zwischen den Zellen).

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
}

Beachten Sie, dass es keinen Grund gibt, ein scrollToOffset aufzurufen oder in Layouts einzutauchen. Das native Bildlaufverhalten macht bereits alles.

Prost alle :)


2
Sie können optional festlegen collectionView.decelerationRate = .fast, dass das Standard-Paging näher beieinander liegt.
Elfanek

1
Das ist wirklich schön. @elfanek Ich habe festgestellt, dass diese Einstellung funktioniert, wenn Sie nicht einen kleinen und sanften Wisch ausführen, dann scheint sie nur schnell zu flackern.
Mylonon

3

Ein bisschen wie Evyas Antwort, aber etwas flüssiger, weil das targetContentOffset nicht auf Null gesetzt wird.

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

2

Swift 5

Ich habe einen Weg gefunden, dies zu tun, ohne UICollectionView zu unterklassifizieren, sondern nur das contentOffset horizontal zu berechnen. Offensichtlich ohne isPagingEnabled auf true gesetzt. Hier ist der Code:

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

Hier ist meine Version davon in Swift 3. Berechnen Sie den Versatz nach dem Ende des Bildlaufs und passen Sie den Versatz mit Animation an.

collectionLayout ist ein 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

Sie können auch eine gefälschte Bildlaufansicht erstellen, um das Scrollen zu handhaben.

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

1

Ändern Sie die Romulo BM-Antwort für das Velocity-Listening

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

0

Ok, die vorgeschlagenen Antworten haben bei mir nicht funktioniert, da ich stattdessen nach Abschnitten scrollen wollte und daher Seitengrößen mit variabler Breite habe

Ich habe das gemacht (nur 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)
            }
        }
    }

In diesem Fall berechne ich jede Seitengröße im Voraus anhand der Abschnittshöhe + Kopf- und Fußzeile und speichere sie im Array. Das ist das pagesSizesMitglied


0

Dies ist meine Lösung in Swift 4.2. Ich wünschte, sie könnte Ihnen helfen.

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

Sie sollten keine Antworten kopieren / einfügen. Markieren Sie es gegebenenfalls als Duplikat.
DonMag

-1

Hier ist meine Vorgehensweise, indem ich a verwende, UICollectionViewFlowLayoutum Folgendes zu überschreiben targetContentOffset:

(Obwohl ich dies letztendlich nicht benutze und stattdessen UIPageViewController verwende.)

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

i erstellt ein benutzerdefinierte Sammlung Ansicht Layout hier , dass die Träger:

  • Paging eine Zelle nach der anderen
  • Paging von 2+ Zellen gleichzeitig in Abhängigkeit von der Wischgeschwindigkeit
  • horizontale oder vertikale Richtungen

es ist so einfach wie:

let layout = PagingCollectionViewLayout()

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

Sie können Ihrem Projekt einfach PagingCollectionViewLayout.swift hinzufügen

oder

fügen Sie pod 'PagingCollectionViewLayout'zu Ihrem podfile

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.