Wie kann ich das Scrollen in einer UIScrollView programmgesteuert erzwingen?

80

Hinweis: Die hier gegebene Antwort funktioniert bei mir nicht.

Ich habe eine UIScrollView (keine Tabellenansicht, nur eine benutzerdefinierte Sache), und wenn der Benutzer bestimmte Aktionen ausführt, möchte ich das Scrollen (Ziehen oder Verzögern) in der Ansicht beenden. Ich habe versucht, zB folgendes zu tun:

[scrollView scrollRectToVisible:CGRectInset([scrollView bounds], 10, 10) animated:NO];

Nach der Theorie, dass bei einem bereits bekannten Rechteck das Scrollen nur dort anhält, wo es ist, aber es stellt sich heraus, dass dies keine Auswirkung hat - anscheinend sieht die Bildlaufansicht, dass das angegebene Rechteck in Grenzen ist und dauert keine Aktion. Ich kann den Bildlauf stoppen lassen, wenn ich ein Rechteck gebe, das definitiv außerhalb der aktuell sichtbaren Grenzen liegt, aber innerhalb der Inhaltsgröße der Ansicht. Dies scheint die Ansicht wie erwartet anzuhalten ... führt aber auch dazu, dass sie an einen anderen Ort springt. Ich könnte wahrscheinlich ein wenig am Rand herumspielen, damit dies einigermaßen funktioniert. Aber kennt jemand einen sauberen Weg, um eine Bildlaufansicht anzuhalten, die ihre Sache macht?

Vielen Dank.

Ben Zotto
quelle

Antworten:

107

Ich habe ein bisschen mit Ihrer ursprünglichen Lösung gespielt, und das scheint gut zu funktionieren. Ich denke, Sie hatten es fast geschafft, aber Sie haben nur das Rechteck ausgeglichen, das Sie zu oft verwendet haben, und vergessen, dass Sie das Rechteck direkt zum ursprünglichen Rechteck zurückblättern können.

Die allgemeine Lösung für jede Bildlaufaktion lautet:

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    offset.x -= 1.0;
    offset.y -= 1.0;
    [scrollView setContentOffset:offset animated:NO];
    offset.x += 1.0;
    offset.y += 1.0;
    [scrollView setContentOffset:offset animated:NO];
}

[Bearbeiten] Ab iOS 4.3 (und möglicherweise früher) scheint dies auch zu funktionieren

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    [scrollView setContentOffset:offset animated:NO];
}
David Liu
quelle
1
David: Großartig, danke. Ich hatte mich in diese Richtung gedreht, aber am Ende hatte ich weniger triviale Berechnungen über die Auswahl eines 1x1-Rect direkt außerhalb der Scroll-Grenzen. Wenn Sie Ihren Vorschlag zum Versetzen und sofortigen Wiederherstellen aufgreifen (was offen gesagt ein unveröffentlichtes Verhalten auszunutzen scheint, bei dem die aufeinanderfolgenden Aufrufe in einem Ereignislauf tatsächlich funktionieren, obwohl das "Ergebnis" ein No-Op sein sollte), funktioniert es einwandfrei. Ich werde Ihre Antwort oben bearbeiten, um die allgemeine Lösung einzuschließen, die für jede Bildlaufrichtung funktionieren sollte. Vielen Dank!
Ben Zotto
(Ich hoffe, Sie haben nichts gegen die Änderungen, wollten für spätere Reisende klären. Vielen Dank für Ihre Antwort!)
Ben Zotto
sieht toll aus ... übrigens, wie kommst du dorthin zurück, wo du warst? Speichern Sie den Offset, bevor Sie ihn beenden, und kehren Sie dann dorthin zurück?
Lior Frenkel
2
In iOS 7 funktioniert bei der Beschleunigung von scrollView nur die Top-Lösung (die Lösung + = 1, - = 1).
Sahil
1
Diese Lösung kann verbessert werden. Verwenden Sie + = 0.1 anstelle von + = 1, sodass ein einziger Aufruf von setContentOffsetausreicht. Die Bildlaufansicht rundet den Inhaltsversatz automatisch auf.
Kelin
89

Die generische Antwort lautet: Das [scrollView setContentOffset:offset animated:NO]ist nicht dasselbe wie [scrollView setContentOffset:offset]!

  • [scrollView setContentOffset:offset animated:NO] stoppt tatsächlich jede laufende Animation.
  • [scrollView setContentOffset:offset] stoppt keine laufende Animation.
  • Gleiches gilt für scrollView.contentOffset = offset: Stoppt keine laufende Animation.

Das ist nirgendwo dokumentiert, aber das ist das Verhalten, wie es unter iOS 6.1 und iOS 7.1 getestet wurde - wahrscheinlich auch vorher.

Die Lösung zum Stoppen einer laufenden Animation / Verzögerung ist also so einfach:

[scrollView setContentOffset:scrollView.contentOffset animated:NO];

Grundsätzlich, was David Liu in seiner bearbeiteten Antwort gesagt hat. Aber ich wollte klarstellen, dass diese beiden APIs NICHT gleich sind.

Swift 3:

scrollView.setContentOffset(scrollView.contentOffset, animated:false)
Calimarkus
quelle
11
Gut gemacht auf die Erklärung zu den "Vernunftsuchenden" unter uns ... (:
Aviel Gross
3
Das macht mich allerdings irgendwie traurig.
DCMaxxx
2
Ich verstehe nicht, warum dieses Zeug nicht dokumentiert ist ... urggggghh
user1105951
Noch interessanter in (mindestens) iOS 11.3 und 11.4 ist: Wenn das contentOffset auf z. B. (300, 0) animiert wird, wird "contentOffset = (300, 0)" zugewiesen, während die Animation noch ausgeführt wird, und das contentOffset wird möglicherweise auf zurückgesetzt (0, 0)! Das muss ein Fehler sein!
Jan
68

Für mich hat die oben akzeptierte Antwort von David Lui bei mir nicht funktioniert. Das habe ich letztendlich getan:

- (void)killScroll {
    self.scrollView.scrollEnabled = NO;
    self.scrollView.scrollEnabled = YES;
}

Für das, was es wert ist, benutze ich den iOS 6.0 iPhone Simulator.

TPoschel
quelle
2
+1 wie du gesagt hast, der andere hat auch bei mir nicht funktioniert, aber diese Lösung hat super funktioniert!
Brenjt
1
Genial, es funktioniert perfekt supportedInterfaceOrientations, um zu verhindern, dass die Bildlaufereignisse fortgesetzt werden, wenn der Benutzer das Gerät während des Bildlaufs dreht. Dies ist manchmal ein Chaos, je nachdem, was Sie tun.
cprcrack
21

Folgendes mache ich für meine Bildlaufansichten und alle anderen verwandten Unterklassen:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
 {
    *targetContentOffset = scrollView.contentOffset;
 }

Damit wird die targetContentOffsetauf den scrollView‚aktuellen Offset, wodurch das Scrollen zu stoppen , weil er das Ziel erreicht hat. Es ist tatsächlich sinnvoll, eine Methode zu verwenden, deren Zweck darin besteht, dass Benutzer das Ziel festlegen können contentOffset.

Schnell

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
}
funct7
quelle
Dies sieht aus wie der eine Apfel, den Entwickler verwenden sollen. weil alle anderen irgendwann zu Fehlern führen. Ich bin überrascht, dass dies nicht die akzeptierte Antwort ist.
OlDor
Dafür ist diese Delegatenmethode gedacht. gute Antwort.
Ryan
Meiner Meinung nach ist dies die eleganteste Lösung, wie Apple in seiner Dokumentation sagt: "Ihre Anwendung kann den Wert des Parameters targetContentOffset ändern, um anzupassen, wo die Bildlaufansicht ihre Bildlaufanimation beendet." Hat sehr gut für mich
funktioniert
Das ist die richtige Antwort. Keines der oben genannten hat für mich
funktioniert
20

Stoppen Sie die Schriftrolle schnell :

scrollView.setContentOffset(scrollView.contentOffset, animated: false)
Budidino
quelle
15

Eigentlich ... Der "modernste" Weg wäre ->

scrollview.panGestureRecognizer.enabled = false;
scrollview.panGestureRecognizer.enabled = true;

Dadurch wird die Gestenerkennung deaktiviert, die für einen Moment für das Scrollen verantwortlich ist, wodurch die aktuelle Berührung beendet wird. Der Benutzer müsste den Finger heben und wieder nach unten legen, um erneut mit dem Scrollen zu beginnen.

Bearbeiten: Dies beendet nur das aktuelle Ziehen des Benutzers, stoppt jedoch nicht sofort die Verzögerung, wenn sich die Bildlaufansicht derzeit in diesem Zustand befindet. Um dies zu tun, ist die Bearbeitung der akzeptierten Antworten so ziemlich der beste Weg xD

[scrollview setContentOffset: scrollview.contentOffset animated:false];
Xatian
quelle
Nun, ich benutze dies in einer meiner Apps und es macht genau das, was vom OP verlangt wird. Können Sie bitte etwas mehr Kontext geben? Was funktioniert nicht oder warum?
Xatian
Oh sorry ... ich glaube ich weiß ... ich habe gerade den Teil der Frage beantwortet, den ich gesucht habe ... den zweiten Teil habe ich übersehen ... :-) -> bearbeitet.
Xatian
Hört nicht auf zu bremsen.
Rudolf Adamkovič
5

Am saubersten ist es, UIScrollView in Unterklassen zu unterteilen und Ihre eigene setContentOffset-Methode bereitzustellen. Dies sollte die Nachricht nur weiterleiten, wenn Sie Ihre freezeboolesche Eigenschaft nicht aktiviert haben.

Wie so:

BOOL freeze; // and the @property, @synthesize lines..

-(void)setContentOffset:(CGPoint)offset
{
    if ( !freeze ) [super setContentOffset:offset];
}

Dann einfrieren:

scrollView.freeze = YES;
mvds
quelle
1
Vielen Dank. Dies stoppt zwar das sichtbare Scrollen, stoppt jedoch nicht die interne Verzögerung. Wenn Sie dies ein- und nach einem Moment wieder ausschalten, wird die Bildlaufpause angezeigt. Springen Sie dann vorwärts und bremsen Sie weiter ab. Der interne Status der Bildlaufansicht wird also immer noch gescrollt. Sie müssen also einfrieren, bis Sie wissen, dass die Verzögerung aufgehört hat, was Sie in Zusammenarbeit mit einem Delegierten tun können, aber es fühlt sich für mich etwas doof an. Ich hoffe auf etwas, das die Verzögerung tatsächlich sofort zappt (z. B. durch ein einziges Tippen auf den Bildschirm).
Ben Zotto
ok, diese Art von Problemen kommt mir aus früheren Erfahrungen bekannt vor ... warum nicht die Verzögerung durch Einstellen decelerationRate = 1e10;während des Einfrierens zappen == JA?
MVDS
(ohne Kenntnis der internen Mathematik, 10 * UIScrollViewDecelerationRateFast ist möglicherweise eine klügere Wahl als 1e10)
mvds
Interessante Idee! Leider scheint dieser Wert geklemmt zu sein. Ich habe es ausprobiert, und es scheint keinen Wert dafür zu geben, der dazu führt, dass die Verzögerung sofort stoppt. Das Beste, was ich tun kann, ist, es träge zu machen. :)
Ben Zotto
Plan B sieht dann vor, das Scrollen für eine kurze Zeit zu deaktivieren - vielleicht können Sie damit durchkommen, da Sie setContentOffset sowieso blockieren und hoffentlich verhindern, dass Nebenwirkungen das Scrollen deaktivieren.
MVDS
4

Diese Antwort hat bei mir funktioniert: Deaktivieren Sie die UIScrollView-Verzögerung

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    [scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
Johnny Rockex
quelle
Dies könnte möglicherweise die scrollView während des Abpralls stoppen
Jakub Truhlář
3

Deaktivieren Sie einfach die Benutzerinteraktion. (schnell)

scrollView.isScrollEnabled = false

Deaktivieren Sie diese Option während der Bildlaufanimation nach dem Ziehen. (schnell)

var scrollingByVelocity = false

func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
    if !scrollingByVelocity {
        scrollView.setContentOffset(scrollView.contentOffset, animated: false)
    }
}
Brownsoo Han
quelle
0

Ich habe diese Methoden in der Sammlungsansicht ausprobiert:

self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()

Kalerfu
quelle
0

Dies funktioniert bei mir in Swift 4.2 :

   func killScroll() {
    self.scrollView.isScrollEnabled = false;
    self.scrollView.isScrollEnabled = true;
}

... als Erweiterung:

extension UIScrollView {
    func killScroll() {
        self.isScrollEnabled = false;
        self.isScrollEnabled = true;

    }
}
StefanLdhl
quelle
0

Ich wollte das Scrollen nur deaktivieren, wenn eine bestimmte UIView in der Scrollansicht die Quelle der Berührung während des Wischens ist. Das Verschieben der UIView außerhalb der UIScrollView hätte einige Umgestaltungen erforderlich gemacht, da wir eine komplexe Ansichtshierarchie hatten.

Um dieses Problem zu umgehen, habe ich der Unteransicht, in der ich das Scrollen verhindern wollte, einen einzelnen UIPanGestureRecognizer hinzugefügt. Dieser UIPanGestureRecognizer cancelsTouchesInViewverhindert, dass die panGesture von UIScrollView aktiviert wird.

Es ist ein bisschen wie ein "Hack", aber es ist eine super einfache Änderung. Wenn Sie ein XIB oder Storyboard verwenden, müssen Sie nur die Schwenkgeste auf die betreffende Unteransicht ziehen.

Daniel Williams
quelle