Jak programowo wymusić zatrzymanie przewijania w widoku UIScrollView?

81

Uwaga: udzielona tutaj odpowiedź nie działa dla mnie.

Mam UIScrollView (nie widok tabeli, tylko niestandardową rzecz), a gdy użytkownik wykonuje określone czynności, chcę zabić przewijanie (przeciąganie lub zwalnianie) w widoku. Próbowałem zrobić np. Tak:

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

na teorii, że biorąc pod uwagę prostokąt, który jest już widoczny, przewijanie zatrzyma się tam, gdzie jest, ale okazuje się, że nie ma to żadnego efektu - najwyraźniej widok przewijania widzi, że dany prostokąt jest w granicach i przyjmuje bez akcji. I można dostać zwój do przystanku, jeśli daję rect, która jest zdecydowanie poza aktualnie niewidzialnych granic, ale wewnątrz contentSize widoku. Wydaje się, że zgodnie z oczekiwaniami powoduje to zatrzymanie widoku ... ale także przeskakuje w inne miejsce. Prawdopodobnie mógłbym trochę bawić się na marginesach, aby to zadziałało w miarę OK, ale czy ktoś zna czysty sposób na zatrzymanie widoku przewijania, który robi swoje?

Dzięki.

Ben Zotto
źródło

Odpowiedzi:

108

Bawiłem się trochę twoim oryginalnym rozwiązaniem i wydaje się, że działa dobrze. Myślę, że prawie go miałeś, ale po prostu odkładałeś prostokąt, którego użyłeś za dużo, i zapomniałeś, że możesz po prostu przewinąć prostokąt prosto z powrotem do oryginalnego.

Uogólnione rozwiązanie dla każdej czynności przewijania jest następujące:

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

[Edytuj] Od iOS 4.3 (i prawdopodobnie wcześniej) to również wydaje się działać

- (void)killScroll 
{
    CGPoint offset = scrollView.contentOffset;
    [scrollView setContentOffset:offset animated:NO];
}
David Liu
źródło
1
David: świetnie, dzięki. Wykręciłem się w tym kierunku, ale skończyło się na mniej banalnych obliczeniach dotyczących wyboru prostokąta 1x1 tuż poza granicami zwoju. Biorąc pod uwagę twoją sugestię przesunięcia, a następnie natychmiastowego przywrócenia (co wydaje się, szczerze mówiąc, wykorzystać nieopublikowane zachowanie, w którym kolejne wywołania w jednym przebiegu zdarzenia faktycznie działają, mimo że „wynik” powinien oznaczać brak działania), działa dobrze. Zamierzam edytować Twoją odpowiedź powyżej, aby uwzględnić uogólnione rozwiązanie, które powinno działać dla dowolnego kierunku przewijania. Dzięki!
Ben Zotto
(Mam nadzieję, że nie masz nic przeciwko zmianom, chciałem wyjaśnić późniejszym podróżnym. Dziękujemy za odpowiedź!)
Ben Zotto
wygląda świetnie ... tak przy okazji, jak wracasz do miejsca, w którym byłeś? czy zapisujesz offset przed zabiciem go, a potem wracasz tam?
Lior Frenkel
2
W systemie iOS 7, gdy scrollView zwalnia, działa tylko najlepsze rozwiązanie (rozwiązanie + = 1, - = 1).
Sahil
1
To rozwiązanie można ulepszyć. Użyj + = 0.1 zamiast + = 1, więc setContentOffsetwystarczy jedno wywołanie . Widok przewijania automatycznie zaokrągla przesunięcie zawartości.
kelin
91

Ogólna odpowiedź brzmi: [scrollView setContentOffset:offset animated:NO]to nie to samo, co [scrollView setContentOffset:offset]!

  • [scrollView setContentOffset:offset animated:NO] faktycznie zatrzymuje każdą uruchomioną animację.
  • [scrollView setContentOffset:offset] nie zatrzymuje żadnej uruchomionej animacji.
  • To samo dotyczy scrollView.contentOffset = offset: nie zatrzymuje żadnej uruchomionej animacji.

Nie jest to nigdzie udokumentowane, ale jest to zachowanie przetestowane na iOS 6.1 i iOS 7.1 - prawdopodobnie również wcześniej.

Zatem rozwiązanie zatrzymania uruchomionej animacji / spowolnienia jest proste:

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

Zasadniczo to, co powiedział David Liu w swojej zredagowanej odpowiedzi. Ale chciałem wyjaśnić, że te dwa API NIE są takie same.

Swift 3:

scrollView.setContentOffset(scrollView.contentOffset, animated:false)
calimarkus
źródło
11
Dobra robota z wyjaśnieniem dla "poszukiwaczy rozumu" pośród nas ... (:
Aviel Gross
3
To trochę mnie zasmuca.
DCMaxxx
2
Nie rozumiem, dlaczego te rzeczy nie są udokumentowane ... pilgggghh
user1105951
Jeszcze bardziej interesujące w (przynajmniej) iOS 11.3 i 11.4 jest: Jeśli contentOffset zostanie animowany do np. (300, 0), wtedy "contentOffset = (300, 0)" jest przypisane, gdy animacja nadal działa, contentOffset może zostać zresetowany do (0, 0)! To musi być błąd!
stycznia
68

Dla mnie zaakceptowana powyżej odpowiedź Davida Lui nie zadziałała. Oto, co ostatecznie zrobiłem:

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

Na ile to jest warte, używam iPhone Simulator iOS 6.0.

TPoschel
źródło
2
+1 tak jak powiedziałeś, drugi też mi nie działał, ale to rozwiązanie działało świetnie!
brenjt
1
Niesamowite, działa doskonale, supportedInterfaceOrientationsaby zapobiec kontynuowaniu zdarzeń przewijania, jeśli użytkownik obraca urządzenie podczas przewijania , co czasami jest bałaganem w zależności od tego, co robisz.
cprcrack
21

Oto, co robię dla moich widoków przewijania i wszystkich innych powiązanych podklas:

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

Ustawia to targetContentOffsetna scrollViewbieżące przesunięcie, powodując zatrzymanie przewijania, ponieważ osiągnęło cel. W rzeczywistości sensowne jest użycie metody, której celem jest ustalenie celu przez użytkowników contentOffset.

Szybki

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = scrollView.contentOffset
}
funct7
źródło
wygląda na to, że Apple powinien używać go programiści. ponieważ wszystkie inne prowadzą w pewnym momencie do błędów. Dziwię się, że to nie jest akceptowana odpowiedź.
OlDor
Do tego służy ta metoda delegata. świetna odpowiedź.
Ryan
Moim zdaniem jest to najbardziej eleganckie rozwiązanie, jak podaje Apple w swojej dokumentacji: „Twoja aplikacja może zmienić wartość parametru targetContentOffset, aby dostosować miejsce, w którym scrollview kończy animację przewijania.”. Pracował dla mnie bardzo dobrze
Macistador
to jest właściwa odpowiedź. Żadne z powyższych nie zadziałało dla mnie
sethu
20

Zatrzymać przewijać SWIFT :

scrollView.setContentOffset(scrollView.contentOffset, animated: false)
budiDino
źródło
16

Właściwie… Najbardziej „nowoczesny” sposób byłby ->

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

To dezaktywuje rozpoznawanie gestów, który jest odpowiedzialny za przewijanie tylko na chwilę, co zabija bieżący dotyk. Użytkownik musiałby podnieść palec i odłożyć go z powrotem, aby ponownie rozpocząć przewijanie.

Edycja: To faktycznie po prostu zabija bieżące przeciąganie użytkownika, ale nie zatrzymuje natychmiast zwalniania, jeśli widok przewijania jest obecnie w tym stanie. Aby to zrobić, poprawna edycja zaakceptowanych odpowiedzi jest w zasadzie najlepszym sposobem xD

[scrollview setContentOffset: scrollview.contentOffset animated:false];
Xatian
źródło
Cóż, używam tego w mojej aplikacji i robi dokładnie to, o co prosi OP. Czy możesz podać trochę więcej kontekstu? Co nie działa lub dlaczego?
Xatian,
O przepraszam ... chyba wiem ... właśnie odpowiedziałem na część pytania, której szukałem ... drugą część przeoczyłem ... :-) -> zredagowałem.
Xatian
Nie zatrzymuje zwalniania.
Rudolf Adamkovič
5

Najczystszym sposobem będzie podklasa UIScrollView i dostarczenie własnej metody setContentOffset. Powinno to przekazać komunikat, tylko jeśli nie włączyłeś swojej freezewłaściwości boolowskiej.

Tak jak to:

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

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

Następnie, aby zamrozić:

scrollView.freeze = YES;
mvds
źródło
1
Dzięki. Chociaż zatrzymuje to widoczne przewijanie, w rzeczywistości nie zatrzymuje wewnętrznego spowolnienia. Jeśli włączysz to, a po chwili ponownie wyłączysz, zobaczysz pauzę przewijania, a następnie przeskoczysz do przodu i dalej zwalniasz. Zatem stan wewnętrzny widoku przewijania nadal się przewija. Dlatego musisz wstrzymać działanie, dopóki nie zorientujesz się, że zwalnianie się zatrzymało, co możesz zrobić we współpracy z delegatem, ale wydaje mi się to trochę głupie. Mam nadzieję na coś, co faktycznie natychmiast zaprzestanie hamowania (np. Ma efekt pojedynczego dotknięcia ekranu).
Ben Zotto,
ok, tego rodzaju problemy brzmią znajomo z wcześniejszych doświadczeń ... dlaczego nie wyłączyć spowolnienia, ustawiając decelerationRate = 1e10;podczas zatrzymania == TAK?
mvds
(nie znając wewnętrznej matematyki, 10 * UIScrollViewDecelerationRateFast może być mądrzejszym wyborem niż 1e10)
mvds
Ciekawy pomysł! Niestety ta wartość wydaje się być ograniczona. Wypróbowałem to i wydaje się, że nie ma takiej wartości, która spowoduje natychmiastowe zatrzymanie hamowania. Najlepsze, co mogę zrobić, to spowolnić. :)
Ben Zotto,
plan B polega na tym, aby wyłączyć przewijanie na krótki czas - być może uda ci się to uciec, ponieważ i tak blokujesz setContentOffset, miejmy nadzieję, że zapobiegniesz wyłączaniu przewijania przez efekty uboczne.
mvds
4

Ta odpowiedź zadziałała dla mnie: Deactivate UIScrollView decelerating

-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
    [scrollView setContentOffset:scrollView.contentOffset animated:YES];
}
Johnny Rockex
źródło
To mogłoby prawdopodobnie zatrzymać scrollView podczas odbicia
Jakub Truhlář
3

Wyłącz tylko przewijanie interakcji użytkownika. (szybki)

scrollView.isScrollEnabled = false

Wyłącz podczas animacji przewijania po przeciągnięciu. (szybki)

var scrollingByVelocity = false

func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
    if !scrollingByVelocity {
        scrollView.setContentOffset(scrollView.contentOffset, animated: false)
    }
}
Brownsoo Han
źródło
0

Wypróbowałem te metody w widoku kolekcji:

self.collectionView.collectionViewLayout.finalizeCollectionViewUpdates()

Kalerfu
źródło
0

To działa dla mnie w Swift 4.2 :

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

... jako rozszerzenie:

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

    }
}
StefanLdhl
źródło
0

Chciałem wyłączyć przewijanie tylko wtedy, gdy określony UIView w widoku przewijania jest źródłem dotyku podczas przesuwania. Przeniesienie UIView poza UIScrollView wymagałoby sporo refaktoryzacji, ponieważ mieliśmy złożoną hierarchię widoków.

Aby obejść ten problem, dodałem pojedynczy UIPanGestureRecognizer do widoku podrzędnego, w którym chciałem zapobiec przewijaniu. Ten UIPanGestureRecognizer będziecancelsTouchesInView zapobiega aktywacji panGesture UIScrollView.

To trochę `` hack '', ale jest to bardzo łatwa zmiana, a jeśli używasz XIB lub Storyboard, wszystko, co musisz zrobić, to przeciągnąć gest panoramowania na dany podwidok.

Daniel Williams
źródło
0

SWift 5+

za pomocą rozszerzenia

extension UIScrollView  {
    
    func stopDecelerating() {
        let contentOffset = self.contentOffset
        self.setContentOffset(contentOffset, animated: false)
    }
}

posługiwać się

    myScrollView.stopDecelerating()
    // your stuff
Lakhdeep Singh
źródło