UIScrollView paging orizzontale come le tabs Safari per dispositivi mobili

Mobile Safari ti consente di cambiare pagina inserendo una sorta di visualizzazione orizzontale del cercapersone UIScrollView con un controllo pagina nella parte inferiore.

Sto cercando di replicare questo particolare comportamento in cui un UIScrollView con scorrimento orizzontale mostra alcuni dei contenuti della vista successiva.

L’esempio fornito da Apple: PageControl mostra come utilizzare un UIScrollView per il paging orizzontale, ma tutte le viste occupano l’intera larghezza dello schermo.

Come ottengo un UIScrollView per mostrare alcuni contenuti della vista successiva come fa Safari mobile?

Un UIScrollView con il paging abilitato si fermerà a multipli della sua larghezza (o altezza) del frame. Quindi il primo passo è capire quanto vuoi che siano le tue pagine. Rendi tale la larghezza di UIScrollView . Quindi, imposta le dimensioni della tua sottoview, per quanto grandi tu ne abbia bisogno, e imposta i loro centri in base ai multipli della larghezza di UIScrollView .

Quindi, dal momento che vuoi vedere le altre pagine, ovviamente, imposta clipsToBounds su NO come dichiarato da mhjoy. La parte del trucco ora è farla scorrere quando l’utente inizia il trascinamento fuori dal range del frame di UIScrollView . La mia soluzione (quando dovevo farlo molto recentemente) era la seguente:

Crea una sottoclass UIView (cioè ClipView ) che conterrà UIScrollView e le sue sotto-visualizzazioni. Essenzialmente, dovrebbe avere la cornice di ciò che si suppone che UIScrollView avrebbe in circostanze normali. Collocare UIScrollView al centro di ClipView . Assicurati che ClipView di clipsToBounds sia impostato su YES se la sua larghezza è inferiore a quella della sua vista genitore. Inoltre, ClipView necessita di un riferimento a UIScrollView .

Il passaggio finale consiste - (UIView *)hitTest:withEvent: all’interno di ClipView .

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

Questo amplia sostanzialmente l’area tattile di UIScrollView al frame della vista del genitore, esattamente ciò di cui hai bisogno.

Un’altra opzione potrebbe essere quella di sottoclass UIScrollView e sovrascrivere il suo punto - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event , tuttavia sarà comunque necessaria una visualizzazione contenitore per eseguire il clipping e potrebbe essere difficile determinare quando per restituire YES basato solo sul frame di UIScrollView .

NOTA: dovresti anche dare un’occhiata a hitTest di Juri Pakaste : withEvent: modifica se hai problemi con l’interazione con gli utenti subview.

La soluzione ClipView sopra ha funzionato per me, ma ho dovuto fare una diversa -[UIView hitTest:withEvent:] . La versione di Ed Marty non ha ottenuto l’interazione dell’utente lavorando con le visualizzazioni di scorrimento verticale che ho in quella orizzontale.

La seguente versione ha funzionato per me:

 -(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { UIView* child = nil; if ((child = [super hitTest:point withEvent:event]) == self) return self.scrollView; return child; } 

Imposta la dimensione della cornice di scrollView come le dimensioni della tua pagina sarebbero:

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

Ora è ansible eseguire il pan di self.view e il contenuto di scrollView scorrerà.
Utilizza anche scrollView.clipsToBounds = NO; per evitare di tagliare il contenuto.

Mi sono ritrovato con il mio UIScrollView personalizzato perché era il metodo più rapido e semplice che mi fosse sembrato. Tuttavia, non ho visto alcun codice esatto, quindi ho pensato che avrei condiviso. I miei bisogni erano per un UIScrollView con contenuti piccoli e quindi lo stesso UIScrollView era piccolo per ottenere l’effetto di paging. Come afferma il post, non puoi scorrere. Ma ora puoi.

Creare una class CustomScrollView e sottoclass UIScrollView. Quindi tutto ciò che devi fare è aggiungerlo nel file .m:

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

Ciò consente di scorrere da un lato all'altro (orizzontale). Regolare i limiti di conseguenza per impostare l'area sensibile a scorrimento / scorrimento. Godere!

Ho fatto un’altra implementazione che può restituire automaticamente la scrollview. Quindi non è necessario avere un IBOutlet che limiterà il riutilizzo del progetto.

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

Ho un’altra modifica potenzialmente utile per l’implementazione hitTest di ClipView. Non mi piaceva dover fornire un riferimento UIScrollView al ClipView. La mia implementazione qui sotto ti permette di riutilizzare la class ClipView per espandere l’area hit-test di qualsiasi cosa, e non doverla fornire con un riferimento.

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

Ho implementato il suggerimento sopravanzato, ma l’ UICollectionView che stavo usando considerava qualsiasi cosa fuori dal frame per essere fuori dallo schermo. Ciò ha causato il rendering delle celle vicine fuori limite quando l’utente stava scorrendo verso di loro, il che non era l’ideale.

Quello che ho finito è stato emulare il comportamento di una scrollview aggiungendo il metodo seguente al delegato (o 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; } 

Questo evita completamente la delega dell’azione swipe, che è stata anche un bonus. UIScrollViewDelegate ha un metodo simile chiamato scrollViewWillEndDragging:withVelocity:targetContentOffset: che può essere utilizzato per visualizzare UITableViews e UIScrollViews.

Abilita l’triggerszione degli eventi tap sulle viste child della vista scroll mentre supporta la tecnica di questa domanda SO. Utilizza un riferimento alla vista di scorrimento (self.scrollView) per la leggibilità.

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

Aggiungi questo alla tua vista figlio per catturare l'evento tattile:

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

(Questa è una variante della soluzione di user1856273. Pulita per la leggibilità e incorporata la correzione di bug di Bartserk. Ho pensato di modificare la risposta di user1856273 ma era una modifica troppo grande da fare.)

la mia versione Premendo il pulsante che si trova sulla pergamena – work =)

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

ma solo [[self subviews] objectAtIndex: 0] deve essere una scroll