2009-08-03 8 views
86

Aplikacja Mobile Safari umożliwia przełączanie stron, wprowadzając rodzaj widoku poziomego stronicowania UIScrollView z kontrolką strony u dołu.UIScrollWyświetl stronicowanie poziome, tak jak karty Mobile Safari.

Próbuję replikować to szczególne zachowanie, gdy przewijany w poziomie UIScrollView pokazuje niektóre z treści następnego widoku.

Przykład podany przez Apple: PageControl pokazuje, jak używać UIScrollView do stronicowania poziomego, ale wszystkie widoki zajmują całą szerokość ekranu.

Jak uzyskać widok UIScrollView, aby pokazać zawartość następnego widoku, takiego jak mobilny Safari?

+2

Tylko myśl ...spróbuj ustawić granice widoku przewijania na mniejsze niż na ekranie, a następnie poćwicz z poprawnym wyświetlaniem widoków. (i ustaw klipy na klipy na NO) – mjhoy

+0

Chciałem mieć strony większe od szerokości widoku uiscroll (przewijanie poziome). A myśl mjhoya naprawdę mi pomogła! – Sasho

+0

Powiązany jest [Paging UIScrollView w przyrostach mniejszych niż rozmiar zawartości] (http://stackoverflow.com/q/1677085/590956). – Sam

Odpowiedz

258

A UIScrollView z włączoną obsługą stronicowania zatrzyma się na wielokrotnościach jego szerokości (lub wysokości) ramki. Pierwszym krokiem jest ustalenie, jak szerokie mają być twoje strony. Ustaw szerokość UIScrollView. Następnie ustaw wymiary swojego podarchiplagania, jakkolwiek są one potrzebne, i ustaw ich centra na podstawie wielokrotności szerokości UIScrollView.

Następnie, ponieważ chcesz zobaczyć inne strony, oczywiście, ustaw clipsToBounds na NO jak mhjoy stwierdził. W tej chwili sztuczka polega na przewijaniu, gdy użytkownik rozpoczyna przeciąganie poza zasięg ramki UIScrollView. Moje rozwiązanie (gdy musiałem zrobić to bardzo niedawno) był następujący:

Tworzenie UIView podklasy (tj ClipView), który będzie zawierał UIScrollView i to subviews. Zasadniczo powinien on mieć ramy tego, co można by założyć, że w normalnych okolicznościach miałoby to być UIScrollView. Umieść UIScrollView w środku ClipView. Upewnij się, że 's clipsToBounds jest ustawiony na YES, jeśli jego szerokość jest mniejsza niż w widoku nadrzędnym. Ponadto, ClipView wymaga odniesienia do UIScrollView.

Ostatnim krokiem jest zastąpienie - (UIView *)hitTest:withEvent: wewnątrz ClipView.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { 
    return [self pointInside:point withEvent:event] ? scrollView : nil; 
} 

To zasadniczo rozszerza dotykowy obszar UIScrollView do ramy widzenia jego rodzica, dokładnie to, czego potrzebują.

Innym rozwiązaniem byłoby podklasy UIScrollView i zastąpić swoją metodę - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event, jednak trzeba jeszcze widok kontenera zrobić wycinek, a to może być trudne do określenia, kiedy powrócić YES opartą wyłącznie na ramie UIScrollView „s.

UWAGA:Należy również przyjrzeć się Juri Pakaste na hitTest:withEvent: modification jeśli masz problemy z interakcji użytkownika subview.

+3

Dzięki Ed. Poszedłem z podklasą 'UIScrollView' do leniwego ładowania widoków (w' layoutSubviews') i użyłem 'clippingRect', aby wykonać test' pointInside: withEvent: '. Działa bardzo dobrze i nie wymaga dodatkowego widoku kontenera. – ohhorob

+1

Świetna odpowiedź. Użyłem podklasy 'UIScrollView' jak @ohhorob, ale z widokiem kontenera dla obcinania i zwracania' [super pointInside: CGPointMake (self.contentOffset.x + self.frame.size.width/2, self.contentOffset.y + self. frame.size.height/2) withEvent: event]; 'in' pointOnside: withEvent:'. Działa to bardzo dobrze i nie ma problemów z interakcją z użytkownikiem wspomnianych w odpowiedzi @JuriPakaste'a. – cduck

+0

Wow, to naprawdę dobre rozwiązanie. Tylko jedno, na mojej instalacji strony, które dodaję, mają UIButtons. Kiedy nadpisuję metodę hitTest, nie pozwala mi ona dotknąć tych przycisków. Mogę przewijać, ale żadna czynność nie może zostać wybrana na żadnej stronie. –

68

Powyższe rozwiązanie pracowało dla mnie, ale musiałem wykonać inną implementację -[UIView hitTest:withEvent:]. Wersja Eda Marty'ego nie uzyskała interakcji użytkownika z pionowymi przewijankami, które mam w poziomie.

Poniższa wersja pracował 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; 
} 
+0

Pomaga to również w uzyskiwaniu przycisków i innych subskrybentów z działaniem użytkownika. :) dziękuje –

+4

Dziękuję za ten kod, w jaki sposób mogę użyć tej modyfikacji, aby uzyskać zdarzenia z subviews (przyciski) nie zawarte w granicach przewijania? Działa, gdy przycisk znajduje się na przykład w granicach centralnego przewijania, ale nie na zewnątrz. – DreamOfMirrors

+4

To naprawdę pomogło. Powinien być włączony jako modyfikacja wybranej odpowiedzi. – Pacu

3

mam inny potencjalnie przydatnych modyfikacji dla realizacji ClipView hitTest. Nie chciałem podawać odniesienia do UIScrollView do ClipView.Moja implementacja poniżej pozwala na ponowne użycie klasy ClipView, aby rozszerzyć obszar testowy wszystkiego i nie trzeba go podawać z odniesieniem.

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

Wprowadziłem kolejną implementację, która może automatycznie przywrócić widok przewijania. Nie musi więc istnieć IBOutlet, który ograniczy ponowne użycie 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; 
} 
+1

Ten jest używany, jeśli masz już xib/storyboard. W przeciwnym razie nie jestem pewien, w jaki sposób uzyskasz odniesienie do widoku stronicowania/przewijania. Jakieś pomysły? – mskw

3

I wdrożone upvoted sugestii powyżej, ale UICollectionView ja stosując uznane nic z ramą się na ekranie. To spowodowało, że pobliskie komórki renderowały się poza granice, gdy użytkownik przewijał się w ich kierunku, co nie było idealne.

W efekcie emulowałem zachowanie przewijania, dodając metodę poniżej 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 uniknąć całkowicie przeniesienia akcji machnięcia, co również było dodatkowym atutem. Model UIScrollViewDelegate ma podobną metodę o nazwie scrollViewWillEndDragging:withVelocity:targetContentOffset:, która może być używana do wyświetlania stron UITableViews i UIScrollViews.

+2

Dave, walczyłem, aby osiągnąć takie zachowanie, używając również UICollectionView i skończyło się na tym samym podejściu, które opisujesz tutaj. Jednak przewijanie nie jest tak dobre, jak w przypadku przewijania z włączoną obsługą stronicowania. Kiedy przesuwałem z większą prędkością, przewijało się kilka stron i zatrzymało się na proponowanej stronie. To, co chcę osiągnąć, to zachowanie zachowane jako normalny widok przewijania z włączoną stronicowaniem - niezależnie od prędkości używam tylko 1 strony więcej. Czy masz jakiś pomysł, jak to zrobić? –

2

moja wersja Naciśnięcie przycisku leżącego na scroll - pracy =)

- (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 [[subviews własny] objectAtIndex: 0] musi być zwój

+0

To rozwiązało mój problem :) Zwróć uwagę, że nie dodajesz przesunięcia przewijania do punktu.x podczas sprawdzania granic, a to sprawia, że ​​kod nie działa poprawnie na poziomych zwojach. Po dodaniu to działało dobrze! – Bartserk

3

umożliwić zapłonów kranu na dziecko widoki widoku przewijania, jednocześnie wspierając technikę tego pytania SO. Używa odniesienia do widoku przewijania (self.scrollView) dla 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 widzenia dziecka uchwycić dotykowy zdarzenie..

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 

(jest to wariacja na temat rozwiązania user1856273 za oczyścić dla czytelności i włączone Bartserk za bug fix myślałem edycji user1856273-tych odpowiedź, ale była to zbyt duża zmiana.)

+0

Aby uruchomić stuknięcie w UIButton osadzonym w widoku podrzędnym, zamieniłem kod hitView = [childViews objectAtIndex: i]; z // znajdź UIButton dla (UIView * paneView w [[childViews objectAtIndex: i] subviews]) { if ([paneView isKindOfClass: [klasa UIButton]]) return paneView; } – CharlesA

5

Skończyłem z niestandardowym UIScrollView, ponieważ to była najszybsza i prostsza metoda, jaką mi się wydawało. Jednak nie widziałem żadnego dokładnego kodu, więc postanowiłem go udostępnić. Moje potrzeby dotyczyły UIScrollView, który miał małą zawartość, a zatem sam UIScrollView był mały, aby uzyskać wpływ na stronicowanie. W post stwierdza, że ​​nie można przesuwać w poprzek. Ale teraz możesz.

Utwórz klasę CustomScrollView i podklasę UIScrollView. Następnie wystarczy dodać to do pliku .m:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { 
    return (point.y >= 0 && point.y <= self.frame.size.height); 
} 

Umożliwia to przewijanie z boku na bok (w poziomie). Dostosuj odpowiednio ograniczenia, aby ustawić obszar dotknięcia dotykiem przesuwania/przewijania. Cieszyć się!

6

rozmiar ramki zestaw Scrollview jako rozmiar stron byłoby:

[self.view addSubview:scrollView]; 
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer]; 

Teraz można przesuwać na self.view i zawartość Scrollview będą przewijane.
Użyj także scrollView.clipsToBounds = NO;, aby zapobiec przycinaniu zawartości.

Powiązane problemy