Jak mogę zmusić stronę, UIScrollView
w której stronicowanie i przewijanie są włączone, w danym momencie poruszają się tylko w pionie lub w poziomie?
Rozumiem, że directionalLockEnabled
właściwość powinna to osiągnąć, ale ukośne przesunięcie nadal powoduje przewijanie widoku po przekątnej zamiast ograniczania ruchu do pojedynczej osi.
Edycja: aby było jaśniej, chciałbym umożliwić użytkownikowi przewijanie w poziomie LUB w pionie, ale nie w obu jednocześnie.
Odpowiedzi:
Muszę przyznać, że jesteś w bardzo trudnej sytuacji.
Zauważ, że musisz użyć UIScrollView z,
pagingEnabled=YES
aby przełączać się między stronami, ale musiszpagingEnabled=NO
przewijać w pionie.Istnieją 2 możliwe strategie. Nie wiem, który będzie działał / jest łatwiejszy do wdrożenia, więc wypróbuj oba.
Po pierwsze: zagnieżdżone UIScrollViews. Szczerze mówiąc, nie widziałem jeszcze osoby, która to zrobiła. Jednak osobiście nie starałem się wystarczająco mocno, a moja praktyka pokazuje, że jeśli wystarczająco się postarasz , możesz sprawić, że UIScrollView zrobi wszystko, co chcesz .
Strategia polega więc na tym, aby zewnętrzny widok przewijania obsługiwał tylko przewijanie w poziomie, a wewnętrzne widoki przewijania tylko w pionie. Aby to osiągnąć, musisz wiedzieć, jak UIScrollView działa wewnętrznie. Zastępuje
hitTest
metodę i zawsze zwraca się, więc wszystkie zdarzenia dotyku trafiają do UIScrollView. Następnie wewnątrztouchesBegan
,touchesMoved
czeki itd to czy jest zainteresowany w wypadku i albo uchwyty lub przekazuje je do wewnętrznych komponentów.Aby zdecydować, czy dotknięcie ma być obsługiwane, czy przekazywane dalej, UIScrollView uruchamia licznik czasu przy pierwszym dotknięciu:
Jeśli nie wykonałeś znacznego ruchu palcem w ciągu 150 ms, zdarzenie to przechodzi do widoku wewnętrznego.
Jeśli znacznie przesunąłeś palec w ciągu 150 ms, zaczyna on przewijać (i nigdy nie przekazuje zdarzenia do widoku wewnętrznego).
Zwróć uwagę, że po dotknięciu tabeli (która jest podklasą widoku przewijania) i natychmiastowym rozpoczęciu przewijania, dotknięty wiersz nigdy nie jest podświetlony.
Jeśli nie przesunąłeś palcem znacząco w ciągu 150 ms i UIScrollView zaczął przekazywać zdarzenia do widoku wewnętrznego, ale następnie przesunąłeś palec na tyle daleko, aby rozpocząć przewijanie, UIScrollView wywołuje
touchesCancelled
widok wewnętrzny i rozpoczyna przewijanie.Zwróć uwagę, że po dotknięciu stołu, przytrzymaniu lekko palca, a następnie rozpoczęciu przewijania, dotknięty wiersz jest najpierw podświetlany, ale później usuwany.
Poniższą sekwencję zdarzeń można zmienić, konfigurując UIScrollView:
delaysContentTouches
jest NIE, to żaden timer nie jest używany - zdarzenia natychmiast przechodzą do wewnętrznej kontroli (ale potem są anulowane, jeśli przesuniesz palcem wystarczająco daleko)cancelsTouches
jest NIE, to po przesłaniu zdarzeń do kontrolki przewijanie nigdy nie nastąpi.Należy pamiętać, że jest to UIScrollView, który odbiera wszystko
touchesBegin
,touchesMoved
,touchesEnded
atouchesCanceled
wydarzenia z Cocoa Touch (bo jegohitTest
mówi to, aby to zrobić). Następnie przekazuje je do widoku wewnętrznego, jeśli chce, tak długo, jak chce.Teraz, gdy wiesz już wszystko o UIScrollView, możesz zmienić jego zachowanie. Mogę się założyć, że preferujesz przewijanie w pionie, tak aby po dotknięciu widoku przez użytkownika i rozpoczęciu (nawet nieznacznego) ruchu palcem widok zaczął przewijać się w kierunku pionowym; ale gdy użytkownik przesunie palec wystarczająco daleko w kierunku poziomym, chcesz anulować przewijanie w pionie i rozpocząć przewijanie w poziomie.
Chcesz podklasować swoją zewnętrzną klasę UIScrollView (powiedzmy, nazywasz swoją klasę RemorsefulScrollView), aby zamiast domyślnego zachowania natychmiast przekazywała wszystkie zdarzenia do widoku wewnętrznego i tylko po wykryciu znacznego ruchu poziomego przewijała.
Jak sprawić, by RemorsefulScrollView zachowywał się w ten sposób?
Wygląda na to, że wyłączenie przewijania w pionie i ustawienie
delaysContentTouches
na NIE powinno sprawić, że zagnieżdżone UIScrollViews będą działać. Niestety tak nie jest; Wydaje się, że UIScrollView wykonuje dodatkowe filtrowanie dla szybkich ruchów (których nie można wyłączyć), więc nawet jeśli UIScrollView można przewijać tylko w poziomie, zawsze pochłania (i ignoruje) wystarczająco szybkie ruchy pionowe.Efekt jest tak poważny, że przewijanie w pionie wewnątrz zagnieżdżonego widoku przewijania jest bezużyteczne. (Wygląda na to, że masz dokładnie taką konfigurację, więc spróbuj: przytrzymaj palec przez 150 ms, a następnie przesuń go w kierunku pionowym - zagnieżdżony UIScrollView działa wtedy zgodnie z oczekiwaniami!)
Oznacza to, że nie możesz używać kodu UIScrollView do obsługi zdarzeń; musisz zastąpić wszystkie cztery metody obsługi dotyku w RemorsefulScrollView i najpierw wykonać własne przetwarzanie, przekazując zdarzenie tylko do
super
(UIScrollView), jeśli zdecydowałeś się na przewijanie w poziomie.Musisz jednak przejść
touchesBegan
do UIScrollView, ponieważ chcesz, aby zapamiętał współrzędną podstawową do przyszłego przewijania w poziomie (jeśli później zdecydujesz, że jest to przewijanie w poziomie). Nie będziesz mógłtouchesBegan
później wysłać do UIScrollView, ponieważ nie możesz przechowywaćtouches
argumentu: zawiera obiekty, które zostaną zmutowane przed następnymtouchesMoved
zdarzeniem i nie możesz odtworzyć starego stanu.Musisz więc natychmiast przejść
touchesBegan
do UIScrollView, ale będziesz ukrywać wszelkie dalszetouchesMoved
zdarzenia, dopóki nie zdecydujesz się przewijać w poziomie. NietouchesMoved
oznacza braku przewijania, więc ten inicjałtouchesBegan
nie zaszkodzi. Ale ustawdelaysContentTouches
na NIE, aby żadne dodatkowe zegary niespodzianki nie przeszkadzały.(Offtopic - w przeciwieństwie do Ciebie, UIScrollView może prawidłowo przechowywać dotknięcia i może
touchesBegan
później odtworzyć i przekazać oryginalne zdarzenie. Ma nieuczciwą przewagę w postaci używania niepublikowanych interfejsów API, więc może klonować obiekty dotykowe przed ich mutacją).Biorąc pod uwagę, że zawsze do przodu
touchesBegan
, musisz także przekazywaćtouchesCancelled
itouchesEnded
. Trzeba skręcićtouchesEnded
wtouchesCancelled
jednak, ponieważ UIScrollView zinterpretujetouchesBegan
,touchesEnded
sekwencję jako dotykowym przyciskiem myszy, a będzie przesyła je do wewnętrznej widzenia. Już sam przekazujesz właściwe zdarzenia, więc nigdy nie chcesz, aby UIScrollView niczego przekazywał.Zasadniczo tutaj jest pseudokod określający to, co musisz zrobić. Dla uproszczenia nigdy nie zezwalam na przewijanie w poziomie po wystąpieniu zdarzenia multitouch.
// RemorsefulScrollView.h @interface RemorsefulScrollView : UIScrollView { CGPoint _originalPoint; BOOL _isHorizontalScroll, _isMultitouch; UIView *_currentChild; } @end // RemorsefulScrollView.m // the numbers from an example in Apple docs, may need to tune them #define kThresholdX 12.0f #define kThresholdY 4.0f @implementation RemorsefulScrollView - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.delaysContentTouches = NO; } return self; } - (id)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { self.delaysContentTouches = NO; } return self; } - (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *result = nil; for (UIView *child in self.subviews) if ([child pointInside:point withEvent:event]) if ((result = [child hitTest:point withEvent:event]) != nil) break; return result; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there's no way to forward it later if (_isHorizontalScroll) return; // UIScrollView is in charge now if ([touches count] == [[event touchesForView:self] count]) { // initial touch _originalPoint = [[touches anyObject] locationInView:self]; _currentChild = [self honestHitTest:_originalPoint withEvent:event]; _isMultitouch = NO; } _isMultitouch |= ([[event touchesForView:self] count] > 1); [_currentChild touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (!_isHorizontalScroll && !_isMultitouch) { CGPoint point = [[touches anyObject] locationInView:self]; if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) { _isHorizontalScroll = YES; [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event] } } if (_isHorizontalScroll) [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll else [_currentChild touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (_isHorizontalScroll) [super touchesEnded:touches withEvent:event]; else { [super touchesCancelled:touches withEvent:event]; [_currentChild touchesEnded:touches withEvent:event]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; if (!_isHorizontalScroll) [_currentChild touchesCancelled:touches withEvent:event]; } @end
Nie próbowałem tego uruchamiać ani nawet kompilować (i napisałem całą klasę w zwykłym edytorze tekstowym), ale możesz zacząć od powyższego i miejmy nadzieję, że zadziała.
Jedynym ukrytym haczykiem, jaki widzę, jest to, że jeśli dodasz jakiekolwiek widoki podrzędne inne niż UIScrollView do RemorsefulScrollView, zdarzenia dotykowe, które przekazujesz do dziecka, mogą wrócić do Ciebie za pośrednictwem łańcucha odpowiedzi, jeśli dziecko nie zawsze obsługuje dotknięcia, tak jak robi to UIScrollView. Kuloodporna implementacja RemorsefulScrollView chroniłaby przed
touchesXxx
ponownym wejściem .Druga strategia: jeśli z jakiegoś powodu zagnieżdżone UIScrollViews nie działają lub okazują się zbyt trudne do poprawienia, możesz spróbować dogadać się tylko z jednym UIScrollView, przełączając jego
pagingEnabled
właściwość w locie zscrollViewDidScroll
metody delegata.Aby zapobiec przewijaniu po przekątnej, należy najpierw spróbować zapamiętać zawartość przesunięcia
scrollViewWillBeginDragging
oraz sprawdzić i zresetować zawartość przesunięcia wewnątrz wscrollViewDidScroll
przypadku wykrycia ruchu po przekątnej. Inną strategią do wypróbowania jest zresetowanie contentSize, aby umożliwić przewijanie tylko w jednym kierunku, gdy zdecydujesz, w którym kierunku zmierza palec użytkownika. (UIScrollView wydaje się dość wybaczający, jeśli chodzi o manipulowanie przy contentSize i contentOffset z jego metod delegata).Jeśli to nie działa albo czy rezultaty w niechlujstwa wizualizacje, trzeba ręcznym
touchesBegan
,touchesMoved
etc, a nie do przodu ukośne zdarzenia ruchu do UIScrollView. (W tym przypadku wrażenia użytkownika będą jednak nieoptymalne, ponieważ będziesz musiał zignorować ruchy po przekątnej zamiast wymuszać je w jednym kierunku. Jeśli czujesz się naprawdę odważny, możesz napisać własny lookalike UITouch, coś w rodzaju RevengeTouch. Cel -C to zwykłe stare C i nie ma na świecie nic bardziej dukktypektywnego niż C; tak długo, jak nikt nie sprawdza prawdziwej klasy obiektów, co, jak sądzę, nikt nie robi, możesz sprawić, że każda klasa będzie wyglądać jak każda inna. możliwość syntezy dowolnych akcentów, z dowolnymi współrzędnymi).Strategia tworzenia kopii zapasowych: istnieje TTScrollView, rozsądna reimplementacja UIScrollView w bibliotece Three20 . Niestety dla użytkownika wydaje się to bardzo nienaturalne i non-ifonish. Ale jeśli każda próba użycia UIScrollView nie powiedzie się, możesz wrócić do niestandardowego widoku przewijania. Odradzam, jeśli to w ogóle możliwe; Korzystanie z UIScrollView zapewnia natywny wygląd i zachowanie, niezależnie od tego, jak ewoluuje w przyszłych wersjach iPhone OS.
Okej, ten krótki esej stał się trochę za długi. Po kilku dniach pracy nad ScrollingMadness nadal interesuję się grami UIScrollView.
PS Jeśli któryś z tych elementów działa i masz ochotę się nim podzielić, wyślij mi e-mail z odpowiednim kodem na adres [email protected], z radością dołączę go do mojej torby ze sztuczkami ScrollingMadness .
PPS Dodanie tego małego eseju do pliku README ScrollingMadness.
źródło
To działa dla mnie za każdym razem ...
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
źródło
Dla leniwych jak ja:
Ustaw
scrollview.directionalLockEnabled
naYES
- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; } - (void) scrollViewDidScroll: (UIScrollView *) scrollView { if (scrollView.contentOffset.x != self.oldContentOffset.x) { scrollView.pagingEnabled = YES; scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, self.oldContentOffset.y); } else { scrollView.pagingEnabled = NO; } } - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; }
źródło
Użyłem następującej metody, aby rozwiązać ten problem, mam nadzieję, że ci pomoże.
Najpierw zdefiniuj następujące zmienne w pliku nagłówkowym kontrolerów.
CGPoint startPos; int scrollDirection;
pozycja_początkowa zachowa contentOffset wartość, gdy delegat otrzyma scrollViewWillBeginDragging wiadomość. Więc w tej metodzie robimy to;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ startPos = scrollView.contentOffset; scrollDirection=0; }
następnie używamy tych wartości do określenia zamierzonego przez użytkownika kierunku przewijania w wiadomości scrollViewDidScroll .
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ if (scrollDirection==0){//we need to determine direction //use the difference between positions to determine the direction. if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){ NSLog(@"Vertical Scrolling"); scrollDirection=1; } else { NSLog(@"Horitonzal Scrolling"); scrollDirection=2; } } //Update scroll position of the scrollview according to detected direction. if (scrollDirection==1) { [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO]; } else if (scrollDirection==2){ [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO]; } }
w końcu musimy zatrzymać wszystkie operacje aktualizacji, gdy użytkownik kończy przeciąganie;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }
źródło
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (decelerate) { scrollDirection=3; } }
zostało zmienione na- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ if (!decelerate) { scrollDirection=0; } }
i- (void)scrollViewDidEndDecelerating{ scrollDirection=0; }
Mogę tu grzebać, ale natknąłem się na ten post dzisiaj, próbując rozwiązać ten sam problem. Oto moje rozwiązanie, wygląda na to, że działa świetnie.
Chcę wyświetlić ekran pełen treści, ale pozwolę użytkownikowi przewijać do jednego z 4 innych ekranów, w górę iw dół w lewo iw prawo. Mam jeden UIScrollView o rozmiarze 320x480 i contentSize 960x1440. Przesunięcie zawartości zaczyna się od 320,480. W scrollViewDidEndDecelerating :, przerysowuję widoki 5 (widoki środkowe i 4 wokół niego) i resetuję przesunięcie zawartości do 320,480.
Oto mięso mojego rozwiązania, metoda scrollViewDidScroll:.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (!scrollingVertically && ! scrollingHorizontally) { int xDiff = abs(scrollView.contentOffset.x - 320); int yDiff = abs(scrollView.contentOffset.y - 480); if (xDiff > yDiff) { scrollingHorizontally = YES; } else if (xDiff < yDiff) { scrollingVertically = YES; } } if (scrollingHorizontally) { scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480); } else if (scrollingVertically) { scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y); } }
źródło
Chłopaki, jest to tak proste, jak ustawienie wysokości podglądu podrzędnego na taką samą, jak wysokość widoku przewijania i wysokość rozmiaru treści. Następnie przewijanie w pionie jest wyłączone.
źródło
Oto moja implementacja strony,
UIScrollView
która zezwala tylko na przewijanie ortogonalne. Pamiętaj, aby ustawićpagingEnabled
sięYES
.PagedOrthoScrollView.h
@interface PagedOrthoScrollView : UIScrollView @end
PagedOrthoScrollView.m
#import "PagedOrthoScrollView.h" typedef enum { PagedOrthoScrollViewLockDirectionNone, PagedOrthoScrollViewLockDirectionVertical, PagedOrthoScrollViewLockDirectionHorizontal } PagedOrthoScrollViewLockDirection; @interface PagedOrthoScrollView () @property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock; @property(nonatomic, assign) CGFloat valueLock; @end @implementation PagedOrthoScrollView @synthesize dirLock; @synthesize valueLock; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self == nil) { return self; } self.dirLock = PagedOrthoScrollViewLockDirectionNone; self.valueLock = 0; return self; } - (void)setBounds:(CGRect)bounds { int mx, my; if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) { // is on even page coordinates, set dir lock and lock value to closest page mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds)); if (mx != 0) { self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal; self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) * CGRectGetHeight(self.bounds)); } else { self.dirLock = PagedOrthoScrollViewLockDirectionVertical; self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) * CGRectGetWidth(self.bounds)); } // show only possible scroll indicator self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical; self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal; } if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) { bounds.origin.y = self.valueLock; } else { bounds.origin.x = self.valueLock; } mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds)); my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds)); if (mx == 0 && my == 0) { // is on even page coordinates, reset lock self.dirLock = PagedOrthoScrollViewLockDirectionNone; } [super setBounds:bounds]; } @end
źródło
Musiałem również rozwiązać ten problem i chociaż odpowiedź Andrieja Tarantowa zdecydowanie zawiera wiele przydatnych informacji, które pomogą zrozumieć, jak
UIScrollViews
działa, uważam, że rozwiązanie jest nieco zbyt skomplikowane. Moje rozwiązanie, które nie obejmuje żadnej podklasy ani przekazywania dotykowego, jest następujące:UIPanGestureRecognizers
, ponownie po jednym dla każdego kierunku.UIScrollViews
właściwościami „panGestureRecognizer i atrapąUIPanGestureRecognizer
odpowiadającą kierunkowi prostopadłemu do żądanego kierunku przewijania UIScrollView przy użyciuUIGestureRecognizer's
-requireGestureRecognizerToFail:
selektora.-gestureRecognizerShouldBegin:
wywołaniach zwrotnych dla fikcyjnych UIPanGestureRecognizers oblicz początkowy kierunek panoramy za pomocą selektora -translationInView: gestu (UIPanGestureRecognizers
nie wywołasz tego wywołania zwrotnego delegata, dopóki dotyk nie przetłumaczy się wystarczająco, aby zarejestrować się jako panoramowanie). Zezwalaj lub nie zezwalaj na uruchamianie aparatu rozpoznawania gestów w zależności od obliczonego kierunku, który z kolei powinien kontrolować, czy będzie można rozpocząć rozpoznawanie prostopadłych gestów panoramowania UIScrollView.-gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
, nie pozwól swojemu manekinowiUIPanGestureRecognizers
działać obok przewijania (chyba że masz jakieś dodatkowe zachowanie, które chciałbyś dodać).źródło
Stworzyłem stronicowanie UIScrollView z ramką o rozmiarze ekranu widoku i ustawiłem rozmiar zawartości na szerokość potrzebną dla całej mojej zawartości i wysokość 1 (dzięki Tonetel). Następnie stworzyłem strony menu UIScrollView z ramkami ustawionymi na każdą stronę, rozmiar ekranu widoku i ustawiłem rozmiar zawartości każdej z nich na szerokość ekranu i niezbędną wysokość dla każdej z nich. Następnie po prostu dodałem każdą ze stron menu do widoku stronicowania. Upewnij się, że stronicowanie jest włączone w widoku przewijania stronicowania, ale wyłączone w widokach menu (powinno być domyślnie wyłączone) i powinno być dobrze.
Widok przewijania stronicowania jest teraz przewijany w poziomie, ale nie w pionie ze względu na wysokość zawartości. Każda strona jest przewijana w pionie, ale nie w poziomie z powodu ograniczeń rozmiaru zawartości.
Oto kod, jeśli to wyjaśnienie pozostawiło coś do życzenia:
UIScrollView *newPagingScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; [newPagingScrollView setPagingEnabled:YES]; [newPagingScrollView setShowsVerticalScrollIndicator:NO]; [newPagingScrollView setShowsHorizontalScrollIndicator:NO]; [newPagingScrollView setDelegate:self]; [newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)]; [self.view addSubview:newPagingScrollView]; float pageX = 0; for (int i = 0; i < NumberOfDetailPages; i++) { CGRect pageFrame = (CGRect) { .origin = CGPointMake(pageX, pagingScrollView.bounds.origin.y), .size = pagingScrollView.bounds.size }; UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here [pagingScrollView addSubview:newPage]; pageX += pageFrame.size.width; }
źródło
Dzięki Tonetel. Nieznacznie zmodyfikowałem Twoje podejście, ale było to dokładnie to, czego potrzebowałem, aby zapobiec przewijaniu w poziomie.
self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
źródło
To ulepszone rozwiązanie icompot, które było dla mnie dość niestabilne. Ten działa dobrze i jest łatwy do wdrożenia:
- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; } - (void) scrollViewDidScroll: (UIScrollView *) scrollView { float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x ); float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y ); if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) ) { scrollView.pagingEnabled = YES; scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, self.oldContentOffset.y); } else { scrollView.pagingEnabled = NO; scrollView.contentOffset = CGPointMake( self.oldContentOffset.x, scrollView.contentOffset.y); } } - (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView { self.oldContentOffset = scrollView.contentOffset; }
źródło
Na przyszłość oparłem się na odpowiedzi Andreya Tanasova i znalazłem następujące rozwiązanie, które działa dobrze.
Najpierw zdefiniuj zmienną do przechowywania contentOffset i ustaw ją na współrzędne 0,0
var positionScroll = CGPointMake(0, 0)
Teraz zaimplementuj następujące elementy w delegacie scrollView:
override func scrollViewWillBeginDragging(scrollView: UIScrollView) { // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method // For some reason, not doing this fails to get the logic to work self.positionScroll = (self.scrollView?.contentOffset)! } override func scrollViewDidScroll(scrollView: UIScrollView) { // Check that contentOffset is not nil // We do this because the scrollViewDidScroll method is called on viewDidLoad if self.scrollView?.contentOffset != nil { // The user wants to scroll on the X axis if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x { // Reset the Y position of the scrollView to what it was BEFORE scrolling started self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y); self.scrollView?.pagingEnabled = true } // The user wants to scroll on the Y axis else { // Reset the X position of the scrollView to what it was BEFORE scrolling started self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!); self.collectionView?.pagingEnabled = false } } }
Mam nadzieję że to pomoże.
źródło
Z dokumentów:
"wartość domyślna to NIE, co oznacza, że przewijanie jest dozwolone zarówno w kierunku poziomym, jak i pionowym. Jeśli wartość wynosi TAK, a użytkownik rozpocznie przeciąganie w jednym ogólnym kierunku (poziomym lub pionowym), widok przewijania wyłącza przewijanie w drugim kierunku. "
Myślę, że ważną częścią jest „jeśli ... użytkownik zacznie przeciągać w jednym ogólnym kierunku”. Więc jeśli zaczną przeciągać po przekątnej, to nie zadziała. Nie żeby te dokumenty były zawsze wiarygodne, jeśli chodzi o interpretację - ale wydaje się, że pasuje do tego, co widzisz.
Wydaje się to rozsądne. Muszę zapytać - dlaczego w dowolnym momencie chcesz ograniczyć się tylko do poziomu lub tylko do pionu? Być może UIScrollView nie jest narzędziem dla Ciebie?
źródło
Mój widok składa się z 10 widoków poziomych, a niektóre z tych widoków składają się z wielu widoków pionowych, więc otrzymasz coś takiego:
1.0 2.0 3.1 4.1 2.1 3.1 2.2 2.3
Z włączonym stronicowaniem na osi poziomej i pionowej
Widok ma również następujące atrybuty:
[self setDelaysContentTouches:NO]; [self setMultipleTouchEnabled:NO]; [self setDirectionalLockEnabled:YES];
Teraz, aby zapobiec przewijaniu po przekątnej, wykonaj następujące czynności:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView { int pageWidth = 768; int pageHeight = 1024; int subPage =round(self.contentOffset.y / pageHeight); if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) { [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight]; } }
U mnie działa jak urok!
źródło
Trzeba zresetować
_isHorizontalScroll
do NIE wtouchesEnded
itouchesCancelled
.źródło
Jeśli masz duży widok z przewijaniem ... i chcesz ograniczyć przewijanie po przekątnej http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html
źródło
Jeśli nie próbowałeś tego robić w wersji beta 3.0, polecam spróbować. Obecnie używam serii widoków tabeli w widoku przewijania i działa zgodnie z oczekiwaniami. (Cóż, przewijanie tak czy inaczej. Znalazłem to pytanie, szukając rozwiązania zupełnie innego problemu ...)
źródło
Dla tych, którzy szukają w Swift drugiego rozwiązania @AndreyTarantsov, wygląda to tak, jak w kodzie:
var positionScroll:CGFloat = 0 func scrollViewWillBeginDragging(scrollView: UIScrollView) { positionScroll = self.theScroll.contentOffset.x } func scrollViewDidScroll(scrollView: UIScrollView) { if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{ self.theScroll.pagingEnabled = true }else{ self.theScroll.pagingEnabled = false } }
tym, co tutaj robimy, jest utrzymanie bieżącej pozycji tuż przed przeciągnięciem widoku scrollview, a następnie sprawdzenie, czy x zwiększyło się lub zmniejszyło w porównaniu z bieżącą pozycją x. Jeśli tak, ustaw pagingEnabled na true, jeśli nie (y wzrosło / spadło), to ustaw pagingEnabled na false
Mam nadzieję, że jest to przydatne dla nowych przybyszów!
źródło
Udało mi się to rozwiązać, implementując scrollViewDidBeginDragging (_ :) i patrząc na prędkość na bazowym UIPanGestureRecognizer.
Uwaga: w tym rozwiązaniu każda panorama, która byłaby przekątna, zostanie zignorowana przez scrollView.
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { super.scrollViewWillBeginDragging(scrollView) let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView) if velocity.x != 0 && velocity.y != 0 { scrollView.panGestureRecognizer.isEnabled = false scrollView.panGestureRecognizer.isEnabled = true } }
źródło
Jeśli używasz narzędzia do tworzenia interfejsów do projektowania interfejsu - można to zrobić dość łatwo.
W konstruktorze interfejsu wystarczy kliknąć UIScrollView i wybrać opcję (w inspektorze), która ogranicza przewijanie tylko w jedną stronę.
źródło