2013-04-12 7 views
5

Quello che voglio ottenere è lo stesso comportamento che questa visione di scorrimento ha:Come fare un UIScrollView scatto alle icone (come App Store: Feature)

App Store feature scroll view (left)

So che questo è utilizzando HTML e non l'API nativa, ma sto cercando di implementarlo come componente UIKit.

Ora, per il comportamento che sto cercando:

  • Si noti che si tratta di una visione di scorrimento di paging, ma la "dimensione della pagina" è minore la larghezza della vista.
  • Quando lo si scorre da sinistra a destra, ciascuna pagina "scatta" sull'elemento più a sinistra.
  • Quando lo si scorre dall'estremità destra a sinistra, "scatta" sull'elemento più a destra.

La stessa pagina, ma ora da destra a sinistra:

App Store feature scroll view (right)

che cosa ho provato:

  • Ho provato a fare la vista di scorrimento più piccolo è super vista e hitTest override, e questo mi ha fatto un comportamento da sinistra a destra.
  • Ho provato a implementare scrollViewWillEndDragging: withVelocity: targetContentOffset: e impostando il targetContentOffset che desidero ma poiché non riesco a cambiare la velocità, scorre semplicemente troppo lentamente o troppo velocemente.
  • Ho provato a implementare scrollViewDidEndDecelerating: e quindi l'animazione all'offset corretto, ma la vista di scorrimento prima si arresta e poi si sposta, non sembra naturale.
  • Ho provato a implementare scrollViewDidEndDragging: willDecelerate: e quindi l'animazione all'offset corretto ma la vista di scorrimento "salta" e non si anima correttamente.

Sono fuori di idee.

Grazie!

Aggiornamento:

ho finito per usare il metodo di Rob Mayoff, sembra pulito. L'ho modificato in modo che funzionasse quando la velocità era 0, ad esempio quando un utente trascina, ferma e rilascia il dito.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
        withVelocity:(CGPoint)velocity 
       targetContentOffset:(CGPoint *)targetContentOffset { 
    CGFloat maxOffset = scrollView.contentSize.width - scrollView.bounds.size.width; 
    CGFloat minOffset = 0; 

    if (velocity.x == 0) { 
     CGFloat targetX = MAX(minOffset,MIN(maxOffset, targetContentOffset->x)); 

     CGFloat diff = targetX - baseOffset; 

     if (ABS(diff) > offsetStep/2) { 
      if (diff > 0) { 
       //going left 
       baseOffset = MIN(maxOffset, baseOffset + offsetStep); 
      } else { 
       //going right 
       baseOffset = MAX(minOffset, baseOffset - offsetStep); 
      } 
     } 
    } else { 
     if (velocity.x > 0) { 
      baseOffset = MIN(maxOffset, baseOffset + offsetStep); 
     } else { 
      baseOffset = MAX(minOffset, baseOffset - offsetStep); 
     } 
    } 

    targetContentOffset->x = baseOffset; 
} 

L'unico problema con questa soluzione è che scorrere la vista di scorrimento non produce l'effetto "rimbalzo". Sembra "bloccato".

+0

Qualsiasi possibilità che ciò possa essere ottenuto utilizzando UITableView/UICollectionView?Voglio lo stesso effetto scroll e snap ma con la tabella/collezione per visualizzare in anteprima gli elementi successivi/precedenti come quello di iTunes. Fare qualcosa come 4 righe in 1 tabella/collezione di colonne. –

+0

Per tutti coloro che desiderano correggere la sensazione "bloccata": se abs (velocity.x) è più grande di dire 0.8, è possibile aggiungere/sottrarre due offsetSteps invece di uno. – lassej

+0

Non dimenticare il modo incredibilmente semplice per farlo. (In particolare con l'autolayout, FACCIA LA VISUALIZZAZIONE SCRIT LITERALE ** SEMPLICEMENTE IL FORMATO DELL'UNITÀ **. E falla pagina. È semplicemente così semplice. Usa una tecnica super-ingegnosa [simile a questa] (http: // stackoverflow.com/a/37505837/294884) se necessario per regolare "area tangibile" – Fattie

risposta

14

Impostazione scrollView.decelerationRate = UIScrollViewDecelerationRateFast, combinato con l'attuazione scrollViewWillEndDragging:withVelocity:targetContentOffset:, sembra funzionare per me usando una vista di raccolta.

In primo luogo, mi concedo alcune variabili d'istanza:

@implementation ViewController { 
    NSString *cellClassName; 
    CGFloat baseOffset; 
    CGFloat offsetStep; 
} 

In viewDidLoad, ho impostato della visualizzazione decelerationRate:

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    cellClassName = NSStringFromClass([MyCell class]); 
    [self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName]; 
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast; 
} 

Ho bisogno offsetStep essere la dimensione di un numero intero di elementi che adattarsi ai limiti sullo schermo della vista. Computo in viewDidLayoutSubviews:

- (void)viewDidLayoutSubviews { 
    [super viewDidLayoutSubviews]; 
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; 
    CGFloat stepUnit = layout.itemSize.width + layout.minimumLineSpacing; 
    offsetStep = stepUnit * floorf(self.collectionView.bounds.size.width/stepUnit); 
} 

Ho bisogno baseOffset al essere l'offset X della vista prima di scorrere inizio. Inizializzo in viewDidAppear::

- (void)viewDidAppear:(BOOL)animated { 
    [super viewDidAppear:animated]; 
    baseOffset = self.collectionView.contentOffset.x; 
} 

poi ho bisogno di forzare la vista per scorrere in passi di offsetStep. Lo faccio in scrollViewWillEndDragging:withVelocity:targetContentOffset:. A seconda di velocity, aumentare o diminuire baseOffset per offsetStep. Ma io morsetto baseOffset a un minimo di 0 e un massimo di contentSize.width - bounds.size.width.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { 
    if (velocity.x < 0) { 
     baseOffset = MAX(0, baseOffset - offsetStep); 
    } else if (velocity.x > 0) { 
     baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep); 
    } 
    targetContentOffset->x = baseOffset; 
} 

Nota che non mi interessa quello che targetContentOffset->x entra come.

Questo ha l'effetto di allineare il bordo sinistro dell'elemento visibile più a sinistra, fino a quando l'utente scorre fino all'ultimo elemento. A quel punto si allinea al bordo destro dell'elemento visibile più a destra, fino a quando l'utente scorre completamente verso sinistra. Questo sembra corrispondere al comportamento dell'app App Store.

Se questo non funziona per voi, si può provare a sostituire l'ultima riga (targetContentOffset->x = baseOffset) con questo:

dispatch_async(dispatch_get_main_queue(), ^{ 
     [scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES]; 
    }); 

che funziona anche per me.

Puoi trovare la mia app di prova in this git repository.

12

È possibile attivare pagingEnabled, o utilizzare decelerationRate = UIScrollViewDecelerationRateFast combinato con scrollViewDidEndDragging e scrollViewDidEndDecelerating (che ridurrà al minimo l'effetto di rallentare lo scorrimento di una posizione e poi animando di nuovo in un'altra posizione). Probabilmente non è esattamente quello che vuoi, ma è piuttosto vicino. E usando animateWithDuration evita il salto istantaneo di quello scatto finale.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
{ 
    if (!decelerate) 
     [self snapScrollView:scrollView]; 
} 

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    [self snapScrollView:scrollView]; 
} 

Si può quindi scrivere una snapScrollView, come ad esempio:

- (void)snapScrollView:(UIScrollView *)scrollView 
{ 
    CGPoint offset = scrollView.contentOffset; 

    if ((offset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) 
    { 
     // no snap needed ... we're at the end of the scrollview 
     return; 
    } 

    // calculate where you want it to snap to 

    offset.x = floorf(offset.x/kIconOffset + 0.5) * kIconOffset; 

    // now snap it to there 

    [UIView animateWithDuration:0.1 
        animations:^{ 
         scrollView.contentOffset = offset; 
        }]; 
} 
5

Soluzione semplice, funziona come l'App Store, con velocità o senza. kCellBaseWidth - larghezza della cella.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
        withVelocity:(CGPoint)velocity 
       targetContentOffset:(inout CGPoint *)targetContentOffset 
{ 
    NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth); 
    targetContentOffset->x = index * kCellBaseWidth; 
} 
+0

Bello e semplice ed effettivamente funziona bene! –

2

Chiming in per Swift. La risposta di @avdyushin è di gran lunga la più semplice e, come menzionato da Ben, funziona molto bene. Anche se ho aggiunto un pezzo dalla risposta di @Rob riguardante la fine della scrollview. Insieme, questa soluzione sembra funzionare perfettamente.

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { 

    if ((scrollView.contentOffset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) { 
     // no snap needed ... we're at the end of the scrollview 
     return 
    } 

    let index: CGFloat = CGFloat(lrintf(Float(targetContentOffset.memory.x)/kCellBaseWidth)) 
    targetContentOffset.memory.x = index * kCellBaseWidth 

} 

è sufficiente aggiungere il minimumLineSpacing-kCellBaseWidth, e voilà.

+0

Ti amo così tanto –

+0

Grazie a tutti coloro che hanno contribuito a questo. SOLO UN PROMEMORIA: se si utilizza un 'minimumLineSpacing' nel proprio FlowLayout, è necessario tenere conto di tale valore. Aggiungilo a kCellBaseWidth. – Womble

Problemi correlati