Mobilna przeglądarka Safari umożliwia przełączanie stron przez wprowadzenie pewnego rodzaju poziomego widoku stronicowania UIScrollView z kontrolką strony u dołu.
Próbuję odtworzyć to szczególne zachowanie, w którym UIScrollView przewijany w poziomie pokazuje część zawartości następnego widoku.
Przykład dostarczony przez Apple: PageControl pokazuje, jak używać UIScrollView do stronicowania poziomego, ale wszystkie widoki zajmują całą szerokość ekranu.
Jak uzyskać widok UIScrollView, aby wyświetlał zawartość następnego widoku, tak jak robi to mobilna przeglądarka Safari?
ios
iphone
cocoa-touch
uiscrollview
pierwszy korespondent
źródło
źródło
Odpowiedzi:
A
UIScrollView
z włączonym stronicowaniem zatrzyma się przy wielokrotnościach szerokości (lub wysokości) ramki. Więc pierwszym krokiem jest ustalenie, jak szerokie mają być Twoje strony. Spraw, aby szerokośćUIScrollView
. Następnie ustaw rozmiary widoku podrzędnego, tak duże, jak potrzebujesz, i ustaw ich środki na podstawie wielokrotnościUIScrollView
szerokości.Następnie, ponieważ chcesz zobaczyć inne strony, oczywiście, zestaw
clipsToBounds
doNO
jak mhjoy stwierdził. Sztuczka polega teraz na tym, że przewija się, gdy użytkownik rozpoczyna przeciąganie poza zakresemUIScrollView
ramki. Moje rozwiązanie (kiedy musiałem to zrobić bardzo niedawno) było następujące:Utwórz
UIView
podklasę (tj.ClipView
),UIScrollView
Która będzie zawierać podglądy i jej podziały. Zasadniczo powinien mieć ramy takie, jakie można by przypuszczaćUIScrollView
w normalnych okolicznościach. UmieśćUIScrollView
w środkuClipView
. Upewnij się, że wartośćClipView
'sclipsToBounds
jest ustawiona na,YES
jeśli jej szerokość jest mniejsza niż szerokość widoku nadrzędnego. PonadtoClipView
potrzeby odnoszą się doUIScrollView
.Ostatnim krokiem jest zastąpienie
- (UIView *)hitTest:withEvent:
wewnątrz plikuClipView
.- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return [self pointInside:point withEvent:event] ? scrollView : nil; }
To w zasadzie rozszerza obszar dotykowy
UIScrollView
do ramy widoku jego rodzica, dokładnie to, czego potrzebujesz.Inną opcją byłoby utworzenie podklasy
UIScrollView
i nadpisanie jej- (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event
metody, jednak nadal będziesz potrzebować widoku kontenera, aby wykonać obcinanie, a określenie, kiedy powrócić, może być trudneYES
tylko na podstawieUIScrollView
ramki.UWAGA: Powinieneś także przyjrzeć się hitTest Juri Pakaste : withEvent: modyfikacja, jeśli masz problemy z interakcją użytkownika subview .
źródło
UIScrollView
podklasą do leniwego ładowania widoków (wlayoutSubviews
) i użyłemclippingRect
dopointInside:withEvent:
testowania. Działa naprawdę dobrze i nie jest wymagany dodatkowy widok kontenera.UIScrollView
podklasy jak @ohhorob ale widokiem pojemnika do przycinania i powrotu[super pointInside:CGPointMake(self.contentOffset.x + self.frame.size.width/2, self.contentOffset.y + self.frame.size.height/2) withEvent:event];
wpointInside:withEvent:
. Działa to bardzo dobrze i nie ma problemów z interakcją użytkownika wspomnianych w odpowiedzi @ JuriPakaste.- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
dodanie do UIScrollViewDelegate w iOS5 oznacza, że nie musisz już przechodzić przez ten palaver.Powyższe
ClipView
rozwiązanie działało u mnie, ale musiałem zrobić inną-[UIView hitTest:withEvent:]
implementację. Wersja Eda Marty'ego nie miała interakcji użytkownika z pionowymi widokami przewijania, które mam wewnątrz poziomego.Następująca wersja zadziałała dla mnie:
-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { UIView* child = nil; if ((child = [super hitTest:point withEvent:event]) == self) return self.scrollView; return child; }
źródło
Ustaw rozmiar ramki scrollView na następujący rozmiar stron:
[self.view addSubview:scrollView]; [self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];
Teraz możesz przesuwać
self.view
, a zawartość scrollView będzie przewijana.Służy również
scrollView.clipsToBounds = NO;
do zapobiegania przycinaniu zawartości.źródło
Skończyło się na tym, że sam zdecydowałem się na niestandardowy UIScrollView, ponieważ był to najszybszy i prostszy sposób, jaki mi się wydawał. Jednak nie widziałem żadnego dokładnego kodu, więc pomyślałem, że się nim podzielę. Moje potrzeby dotyczyły UIScrollView, który miał małą zawartość, a zatem sam UIScrollView był mały, aby uzyskać efekt stronicowania. Jak stwierdza post, nie możesz przesuwać palcem. Ale teraz możesz.
Utwórz klasę CustomScrollView i podklasę UIScrollView. Następnie wszystko, co musisz zrobić, to dodać to do pliku .m:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return (point.y >= 0 && point.y <= self.frame.size.height); }
Pozwala to na przewijanie z boku na bok (poziomo). Dostosuj odpowiednio granice, aby ustawić obszar dotykowy przesuwania / przewijania. Cieszyć się!
źródło
Zrobiłem kolejną implementację, która może automatycznie zwrócić scrollview. Nie trzeba więc mieć IBOutlet, który ograniczy ponowne wykorzystanie w projekcie.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if ([self pointInside:point withEvent:event]) { for (id view in [self subviews]) { if ([view isKindOfClass:[UIScrollView class]]) { return view; } } } return nil; }
źródło
Mam inną potencjalnie przydatną modyfikację dla implementacji hitTest ClipView. Nie podobało mi się konieczność dostarczania odwołania UIScrollView do ClipView. Poniższa implementacja umożliwia ponowne użycie klasy ClipView w celu rozszerzenia obszaru testów trafień czegokolwiek bez konieczności dostarczania do niego odwołania.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (([self pointInside:point withEvent:event]) && (self.subviews.count >= 1)) { // An extended-hit view should only have one sub-view, or make sure the // first subview is the one you want to expand the hit test for. return [self.subviews objectAtIndex:0]; } return nil; }
źródło
Zaimplementowałem powyżej przegłosowaną sugestię, ale używałem, aby
UICollectionView
wszystko poza ramką znalazło się poza ekranem. Spowodowało to, że pobliskie komórki renderowały się poza granice tylko wtedy, gdy użytkownik przewijał je w ich kierunku, co nie było idealne.Skończyło się na tym, że emulowałem zachowanie widoku przewijania, dodając poniższą metodę do delegata (lub
UICollectionViewLayout
).- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { if (velocity.x > 0) { proposedContentOffset.x = ceilf(self.collectionView.contentOffset.x / pageSize) * pageSize; } else { proposedContentOffset.x = floorf(self.collectionView.contentOffset.x / pageSize) * pageSize; } return proposedContentOffset; }
Pozwala to całkowicie uniknąć delegowania operacji machnięcia, co również było bonusem.
UIScrollViewDelegate
Ma podobną metodę zwanąscrollViewWillEndDragging:withVelocity:targetContentOffset:
które mogłyby zostać wykorzystane do UITableViews stron i UIScrollViews.źródło
Włącz wyzwalanie zdarzeń dotknięcia w widokach potomnych widoku przewijania, jednocześnie wspierając technikę tego pytania SO. Używa odniesienia do widoku przewijania (self.scrollView) w celu zwiększenia czytelności.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *hitView = nil; NSArray *childViews = [self.scrollView subviews]; for (NSUInteger i = 0; i < childViews.count; i++) { CGRect childFrame = [[childViews objectAtIndex:i] frame]; CGRect scrollFrame = self.scrollView.frame; CGPoint contentOffset = self.scrollView.contentOffset; if (childFrame.origin.x + scrollFrame.origin.x < point.x + contentOffset.x && point.x + contentOffset.x < childFrame.origin.x + scrollFrame.origin.x + childFrame.size.width && childFrame.origin.y + scrollFrame.origin.y < point.y + contentOffset.y && point.y + contentOffset.y < childFrame.origin.y + scrollFrame.origin.y + childFrame.size.height ){ hitView = [childViews objectAtIndex:i]; return hitView; } } hitView = [super hitTest:point withEvent:event]; if (hitView == self) return self.scrollView; return hitView; }
Dodaj to do widoku dziecka, aby uchwycić zdarzenie dotykowe:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
(Jest to odmiana rozwiązania użytkownika 1856273. Oczyszczono ją pod kątem czytelności i uwzględniono poprawkę błędu Bartserka. Myślałem o edytowaniu odpowiedzi użytkownika1856273, ale zmiana była zbyt duża).
źródło
moja wersja Naciśnięcie przycisku leżącego na zwoju - praca =)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView* child = nil; for (int i=0; i<[[[[self subviews] objectAtIndex:0] subviews] count];i++) { CGRect myframe =[[[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i] frame]; CGRect myframeS =[[[self subviews] objectAtIndex:0] frame]; CGPoint myscroll =[[[self subviews] objectAtIndex:0] contentOffset]; if (myframe.origin.x < point.x && point.x < myframe.origin.x+myframe.size.width && myframe.origin.y+myframeS.origin.y < point.y+myscroll.y && point.y+myscroll.y < myframe.origin.y+myframeS.origin.y +myframe.size.height){ child = [[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i]; return child; } } child = [super hitTest:point withEvent:event]; if (child == self) return [[self subviews] objectAtIndex:0]; return child; }
ale tylko [[self subviews] objectAtIndex: 0] musi być zwojem
źródło
Oto szybka odpowiedź oparta na odpowiedzi Eda Marty'ego, ale zawierająca również modyfikację Juri Pakaste, aby umożliwić naciskanie przycisków itp. W widoku przewijania.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) return view == self ? scrollView : view }
źródło