Erkennen von UIScrollView-Seitenänderungen

Antworten:

17

Implementieren Sie den Delegaten von UIScrollView. Diese Methode ist genau das, wonach Sie suchen.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Viraj
quelle
28
Das Erkennen des Seitenwechsels bei dieser Methode ist nicht effizient, da der Code jedes Mal ausgeführt wird, wenn der Benutzer wischt. Daher ist es besser, dies mit der scrollViewDidEndDecelerating-Methode zu tun.
Jianpx
2
@jianpx - Diese Lösung hat ein Problem: Wenn der Benutzer genau am Ende der Seite aufhört zu scrollen (dies ist durchaus möglich), wird diese Funktion nicht aufgerufen!
Maciej Kozieł
@ MaciejKozieł Haben Sie eine Lösung für dieses Problem gefunden?
Zorayr
@Zorayr Ja, ich habe aufgehört, mir darüber Sorgen zu machen;) Ich kann mir nur wenige Alternativen vorstellen (und die ursprüngliche Antwort ist zwar weniger effizient, aber eine davon). Ich brauchte es für die Seitenanzeige, und dieser Randfall ist kein App-Breaking-Problem. Es wird aktualisiert, sobald eine Verzögerung auftritt.
Maciej Kozieł
@jianpx du hast mit diesem Kommentar ein ganz bestimmtes Problem für mich gelöst. Vielen Dank.
StoriKnow
193

Verwenden Sie diese Option, um festzustellen, welche Seite gerade angezeigt wird, und führen Sie beim Seitenwechsel eine Aktion aus:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    static NSInteger previousPage = 0;
    CGFloat pageWidth = scrollView.frame.size.width;
    float fractionalPage = scrollView.contentOffset.x / pageWidth;
    NSInteger page = lround(fractionalPage);
    if (previousPage != page) {
        // Page has changed, do your thing!
        // ...
        // Finally, update previous page
        previousPage = page;
    }
}

Wenn es für Sie akzeptabel ist, erst dann auf den Seitenwechsel zu reagieren, wenn der Bildlauf vollständig gestoppt ist, ist es am besten, die obigen Schritte innerhalb der scrollViewDidEndDecelerating:Delegate-Methode anstelle der scrollViewDidScroll:Methode auszuführen.

Michael Wasserfall
quelle
4
Ich weiß, dass dies alt ist, aber vielleicht hilft es, wenn Sie diese Methode verwenden, können negative Zahlen und auch Seiten zurückgegeben werden, die außerhalb der Grenzen der Bildlaufansicht liegen. Wenn Sie die Seite für den Zugriff auf ein Array verwenden, kann dies offensichtlich zu Problemen führen. Daher sind möglicherweise Überprüfungen auf der Seite erforderlich.
ScottPetit
2
Das Schreiben des obigen Codes wäre viel besser ... - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
Nishant
1
Ich verstehe nicht, warum diese Antwort gewählt wird. Die Verwendung des oben genannten wird bei jeder Bewegung der Bildlaufansicht aufgerufen. Das ist wirklich nicht nötig. Die Verwendung scrollViewDidEndDeceleratingist eine bessere, aber keine vollständige Lösung. Ich habe eine eigene Lösung hinzugefügt, die das Problem für mich vollständig gelöst hat.
Segev
1
Dies hängt davon ab, wann sie auf einen Seitenwechsel reagieren möchten. Wenn sie nur wissen müssen, wann die Bildlaufansicht aufgehört hat zu scrollen, ist es am besten, dies nach dem Abbremsen zu tun. Ansonsten ist die did scroll-Methode am besten.
Michael Wasserfall
@segev - scrollViewDidEndDecelerating funktioniert nicht.
Fattie
64

In der paging-fähigen Bildlaufansicht können Sie scrollViewDidEndDecelerating verwenden, um festzustellen , wann die Ansicht auf einer Seite festgelegt ist (möglicherweise dieselbe Seite).

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

scrollViewDidScrollwird bei jeder Bewegung aufgerufen. Im Zusammenhang mit Paging kann die aktivierte Ansicht verwendet werden, um festzustellen, wann ein Bildlauf durchgeführt wurde, um zur nächsten Seite zu gelangen (wenn das Ziehen an diesem Punkt gestoppt wird).

Ankur Badola
quelle
2
Diese Lösung hat ein Problem: Wenn der Benutzer genau am Ende der Seite aufhört zu scrollen (dies ist durchaus möglich), wird diese Funktion nicht aufgerufen!
Maciej Kozieł
@ MaciejKozieł Eine bessere Lösung ist in einer anderen Antwort von mir
Segev
@Sagev Wie löst es diesen Fall, wo - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollViewund - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollViewüberhaupt nicht aufgerufen werden?
Maciej Kozieł
42

Wie wäre es mit der Kombination von zwei Methoden von UIScrollViewDelegate?

In scrollViewDidEndDragging(_:willDecelerate:), wenn es sofort aufhört, machen wir die Seite Berechnung; Wenn es langsamer wird, lassen wir es los und es wird von gefangen scrollViewDidEndDecelerating(_:).

Der Code wird mit XCode Version 7.1.1, Swift Version 2.1 getestet

class ViewController: UIViewController, UIScrollViewDelegate {

  // MARK: UIScrollViewDelegate
  func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate == false {
        let currentPage = scrollView.currentPage
        // Do something with your page update
        print("scrollViewDidEndDragging: \(currentPage)")
    }
  }

  func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    let currentPage = scrollView.currentPage
    // Do something with your page update
    print("scrollViewDidEndDecelerating: \(currentPage)")
  }

}

extension UIScrollView {
   var currentPage: Int {
      return Int((self.contentOffset.x+ (0.5*self.frame.size.width))/self.frame.width)+1
   }
}
Cullen SUN
quelle
Dies funktioniert nicht, wenn Sie sowohl vertikale als auch horizontale Gesten aktiviert haben.
Sumit Gera
24

Das erste Ergebnis bei Google zur Seitenerkennung, daher musste ich dies meiner Meinung nach mit einer besseren Lösung beantworten. (Auch wenn diese Frage vor zweieinhalb Jahren gestellt wurde.)

Ich würde es vorziehen, nicht nur anzurufen scrollViewDidScroll, um die Seitenzahl zu verfolgen. Das ist ein Overkill für so etwas Einfaches. Die Verwendung scrollViewDidEndDeceleratingfunktioniert und stoppt auf einer Seite, ABER (und es ist ein großes, aber), wenn der Benutzer zweimal etwas schneller als normal über den Bildschirm wischt, scrollViewDidEndDeceleratingwird nur einmal aufgerufen. Sie können problemlos von Seite 1 zu Seite 3 wechseln, ohne Seite 2 zu verarbeiten.

Das hat es für mich komplett gelöst:

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    scrollView.userInteractionEnabled = NO;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    //Run your code on the current page
    scrollView.userInteractionEnabled = YES;
}

Auf diese Weise kann der Benutzer jeweils nur eine Seite ohne das oben beschriebene Risiko streichen.

Segev
quelle
Du hast vollkommen Recht. Dies ist eine gute Lösung für unendliche Schriftrollen.
Fabrizotus
2
Wenn der Benutzer versucht, mehrere Seiten schnell hintereinander zu wischen, verhält er sich nicht wie erwartet. Dies ist zwar nicht perfekt, aber für mich eine schnelle Lösung, bis ich mehr Zeit habe, mich einer besseren Lösung zu widmen.
Pier-Luc Gendreau
@ Pier-LucGendreau So weit meine Tests gehen, gibt es kein solches Szenario. Der Benutzer kann nicht zweimal wischen. Wenn Sie einen Haltepunkt scrollViewWillBeginDeceleratinghineinstecken, werden Sie feststellen, dass der Benutzer in der Sekunde, in der der Benutzer seinen Finger vom ersten Wischen hebt, diesen Haltepunkt erreicht.
Segev
2
Ich weiß, aber wenn Sie erneut wischen, bevor die erste Wischanimation beendet ist, geht dieser zweite Wisch verloren.
Pier-Luc Gendreau
1
Vielen Dank, sehr geehrter Herr, dass Sie die Verantwortung für die Verbesserung einer alten Antwort übernommen haben.
TigerCoding
18

Swift 4

Ich fand den besten Weg, dies zu tun, ist mit scrollViewWillEndDragging(_:withVelocity:targetContentOffset:). Sie können vorhersagen, ob Paging auftreten wird, sobald Sie Ihren Finger vom Bildschirm heben. Dieses Beispiel dient zum horizontalen Blättern.

Denken Sie daran, das scrollView.delegatedem Objekt zuzuweisen , das UIScrollViewDelegatediese Methode übernimmt und implementiert.

var previousPageXOffset: CGFloat = 0.0

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

    let targetOffset = targetContentOffset.pointee

    if targetOffset.x == previousPageXOffset {
        // page will not change
    } else if targetOffset.x < previousPageXOffset {
        // scroll view will page left
    } else if targetOffset.x > previousPageXOffset {
        // scroll view will page right
    }

    previousPageXOffset = targetOffset.x
    // If you want to track the index of the page you are on just just divide the previousPageXOffset by the scrollView width.
    // let index = Int(previousPageXOffset / scrollView.frame.width)


}
Doug Galante
quelle
2
Das ist die richtige Antwort. Die anderen Methoden werden aufgerufen, auch wenn eine scrollView nicht ausreichend scrollt, um die Paging-Änderung auszulösen.
Romulo BM
2

Hier ist die schnelle Lösung dafür.

Machen Sie zwei Eigenschaften currentPage und previousPage in der Klasse, in der Sie Ihren Code implementieren, und initialisieren Sie sie mit 0.

Aktualisieren Sie jetzt currentPage von scrollViewDidEndDragging ( : willDecelerate :) und scrollViewDidEndDecelerating ( : scrollView :).

Und aktualisieren Sie dann die vorherige Seite in scrollViewDidEndScrollingAnimation (_: scrollView :)

    //Class Properties
    var currentPage = 0
    var previousPage = 0

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        updatePage(scrollView)
        return
    }

 func scrollViewDidEndDecelerating(scrollView: UIScrollView){
        updatePage(scrollView)
        return
    }


 func updatePage(scrollView: UIScrollView) {
        let pageWidth:CGFloat = scrollView.frame.width
        let current:CGFloat = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth)+1
        currentPage = Int(current)

        if currentPage == 0 {
              // DO SOMETHING
        }
        else if currentPage == 1{
              // DO SOMETHING

        }
    }

func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
     if previousPage != currentPage {
          previousPage = currentPage
          if currentPage == 0 {
              //DO SOMETHING
             }else if currentPage == 1 {
               // DO SOMETHING
           }
       }
   }
Rohit Tomar
quelle
1

Ausschneiden und Einfügen für 2019

Das ist nicht so einfach:

var quantumPage: Int = -100 {   // the UNIQUELY LANDED ON, NEVER REPEATING page
    didSet {
        print(">>>>>> QUANTUM PAGE IS \(quantumPage)")
        pageHasActuallyChanged() // your function
    }
}

private var possibleQuantumPage: Int = -100 {
    didSet {
        if oldValue != possibleQuantumPage {
            quantumPage = possibleQuantumPage
        }
    }
}

public func scrollViewDidEndDragging(
                    _ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate == false {
        possibleQuantumPage = currentPageEvenIfInBetween
    }
}

public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    possibleQuantumPage = currentPageEvenIfInBetween
}

var currentPageEvenIfInBetween: Int {
   return Int((self.contentOffset.x + (0.5 * self.frame.width)) / self.frame.width)
}

Funktioniert perfekt.

pageHasActuallyChangedwird nur aufgerufen, wenn der Benutzer Seiten wechselt, was Menschen als "Seitenwechsel" betrachten würden.

Seien Sie sich bewusst: Das Aufrufen ist schwierig:

Dies ist zum Zeitpunkt des Starts schwierig zu initialisieren und hängt davon ab, wie Sie das ausgelagerte System verwenden .

In jedem ausgelagerten System haben Sie höchstwahrscheinlich so etwas wie "scrollToViewAtIndex ..."

open func scrollToViewAtIndexForBringup(_ index: Int) {
    if index > -1 && index < childViews.count {
        
        let w = self.frame.size.width
        let h = self.frame.size.height
        
        let frame = CGRect(x: CGFloat(index)*w, y: 0, width: w, height: h)
        scrollRectToVisible(frame, animated: false) // NOTE THE FALSE
        
        // AND IMPORTANTLY:
        possibleQuantumPage = currentPageEvenIfInBetween
    }
}

Wenn der Benutzer das "Buch" auf Seite 17 öffnet, rufen Sie in Ihrer Bossklasse diese Funktion auf, um es beim Aufrufen auf "17" zu setzen.

In einem solchen Beispiel , würden Sie müssen nur daran denkendass Sie müssen zunächst unsere possibleQuantumPage Wert in solchen bringup Funktionen eingestellt; Es gibt keinen wirklich verallgemeinerten Weg, um mit der Ausgangssituation umzugehen.

Schließlich möchten Sie vielleicht nur "schnell" zur Startseite scrollen und wer weiß, was das in einer quantumPage-Situation "bedeutet". Stellen Sie daher sicher, dass Sie Ihr Quantenseitensystem während des Startvorgangs entsprechend Ihrer Situation sorgfältig initialisieren.

Kopieren Sie auf jeden Fall einfach die fünf Funktionen oben und fügen Sie sie ein, um ein perfektes Quanten-Paging zu erhalten.

Fattie
quelle
0

Für Swift

static var previousPage: Int = 0
func scrollViewDidScroll(_ scrollView: UIScrollView){
    let pageWidth: CGFloat = scrollView.frame.width
    let fractionalPage: CGFloat = scrollView.contentOffset.x / pageWidth
    let page = lround(Double(fractionalPage))
    if page != previousPage{
        print(page)
        // page changed
    }
}
Hiren Panchal
quelle
0
var scrollViewPage = 0
override func viewDidLoad() {
    super.viewDidLoad()
    scrollViewPage = scrollView.currentPage
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    if scrollViewPage != scrollView.currentPage {
        scrollViewPage = scrollView.currentPage
        // Do something with your page update
        print("scrollViewDidEndDecelerating: \(scrollViewPage)")
    }
}

Und Erweiterung verwenden

extension UIScrollView {
    var currentPage: Int {
        return Int((self.contentOffset.x + (0.5 * self.frame.size.width)) / 
        self.frame.width) + 1
    }
}
Saad Tahir
quelle