2013-04-12 6 views
5

Was ich will ist das gleiche Verhalten bekommen, dass diese Scroll-Ansicht hat:Wie ein UIScrollView zu Ikonen Snap machen (wie App Store: Feature)

App Store feature scroll view (left)

Ich weiß, dass dies mit Hilfe von HTML und nicht die native API, aber ich versuche, sie als UIKit-Komponente zu implementieren. Jetzt

, auf das Verhalten, die ich suche:

  • Beachten Sie, dass es sich um eine ausgelagerte Scroll-Ansicht ist, aber die „Seitengröße“ ist kleiner als die Breite der Ansicht.
  • Wenn Sie einen Bildlauf von links nach rechts durchführen, "schnappt" jede Seite auf das Element ganz links.
  • Wenn Sie vom rechten Ende nach links blättern, "schnappt" das Objekt am weitesten rechts.

Die gleiche Seite, aber jetzt von rechts nach links:

App Store feature scroll view (right)

Was ich habe versucht:

  • Ich habe versucht, so dass die Bildlaufansicht kleiner als es ist Super View und Overriding HitTest, und das hat mir dieses Links-zu-Rechts-Verhalten gebracht.
  • ich scrollViewWillEndDragging habe versucht Umsetzung: withVelocity: targetContentOffset: und Einstellung der targetContentOffset ich will, aber da ich die Geschwindigkeit nicht ändern kann es langsam oder zu schnell einfach zu scrollt.
  • Ich habe versucht scrollViewDidEndDecelerating Umsetzung: und dann auf die richtige Animieren versetzt aber die Scroll-Ansicht stoppt zuerst bewegt sich dann, es nicht natürlich aussehen.
  • Ich habe versucht scrollViewDidEndDragging Umsetzung: willDecelerate: und dann auf die richtige Animieren versetzt aber die Scroll-Ansicht „springt“ und animieren nicht richtig.

Ich habe keine Ideen mehr.

Danke!

Update:

I Rob Mayoff Methode mit endete, sieht es sauber. Ich habe es so geändert, dass es funktioniert, wenn die Geschwindigkeit 0 ist, zum Beispiel wenn ein Benutzer den Finger zieht, stoppt und den Finger loslässt.

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

Das einzige Problem bei dieser Lösung besteht darin, dass die Scroll-Ansicht klauen nicht die „Bounce“ -Effekt erzeugt. Es fühlt sich "fest" an.

+0

Eine Chance, dass dies mit einer UITableView/UICollectionView erreicht werden könnte?Ich möchte den gleichen Scroll- und Snap-Effekt, aber mit der Tabelle/Sammlung, um die nächsten/vorherigen Elemente wie die in iTunes anzuzeigen. So etwas wie 4 Zeilen in 1 Spalte Tabelle/Sammlung zu tun. –

+0

Für alle, die das "stecken gebliebene" Gefühl beheben möchten: Wenn abs (velocity.x) größer als beispielsweise 0.8 ist, können Sie zwei offsetSteps statt eins addieren/subtrahieren. – lassej

+0

Vergessen Sie nicht die unglaublich einfache Art, dies zu tun. (Besonders mit Auto-Layout. NUR DIE LITERAL SCROLL VIEW, ** Einfach die Größe der "Einheit" **. Und machen Sie es Seite. Es ist nur so einfach. Verwenden Sie super-geniale [Technik etwas wie das] (http: // stackoverflow.com/a/37505837/294884) wenn nötig, um "touchable area" anzupassen – Fattie

Antwort

14

Einstellung scrollView.decelerationRate = UIScrollViewDecelerationRateFast, kombiniert mit der Umsetzung scrollViewWillEndDragging:withVelocity:targetContentOffset:, für mich eine Sammlung Ansicht scheint zu funktionieren mit.

Zuerst gebe ich mich einige Instanzvariablen:

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

In viewDidLoad, habe ich die Ansicht des decelerationRate:

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

I offsetStep brauchen die Größe einer ganzen Zahl von Elementen zu sein, dass passen in die Bildschirmgrenzen der Ansicht. Ich berechnen es 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); 
} 

Ich brauche baseOffset auf die das X vor Scrollen beginnt der Ansicht, ausgeglichen werden. Ich initialisieren es in viewDidAppear::

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

Dann muss ich die Ansicht zwingen, in Schritten von offsetStep zu blättern. Ich mache das in scrollViewWillEndDragging:withVelocity:targetContentOffset:. Abhängig von der velocity, ich erhöhen oder verringern baseOffset von offsetStep. Aber ich spalte baseOffset auf ein Minimum von 0 und ein Maximum von 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; 
} 

Beachten Sie, dass ist mir egal, was targetContentOffset->x kommt als.

Dies hat den Effekt, dass an der linken Kante des linken sichtbaren Elements ausgerichtet wird, bis der Benutzer zum letzten Element scrollt. An diesem Punkt wird es an der rechten Kante des am weitesten rechts sichtbaren Elements ausgerichtet, bis der Benutzer ganz nach links scrollt. Dies scheint mit dem Verhalten der App Store-App übereinzustimmen.

Wenn das nicht für Sie arbeiten, können Sie versuchen, die letzte Zeile zu ersetzen (targetContentOffset->x = baseOffset) mit diesem:

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

, die auch für mich funktioniert.

Sie können meine Testapp in this git repository finden.

12

Sie können entweder einschalten pagingEnabled oder decelerationRate = UIScrollViewDecelerationRateFast mit scrollViewDidEndDragging kombiniert verwenden und scrollViewDidEndDecelerating (was den Effekt der Verlangsamung zu einem Ort minimieren Scrollen und dann wieder an einem anderen Ort zu animieren). Es ist wahrscheinlich nicht genau, was Sie wollen, aber es ist ziemlich nah dran. Und durch animateWithDuration mit vermeidet es die momentanen Springen des endgültigen Verschlusses.

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

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

können Sie dann ein snapScrollView, wie schreiben:

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

Einfache Lösung, funktioniert wie App Store, mit Geschwindigkeit oder ohne es. kCellBaseWidth - Breite der Zelle.

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

Schön und einfach und eigentlich funktioniert ok! –

2

Chiming für Swift. @avdyushin 's Antwort ist bei weitem die einfachste und, wie Ben erwähnt, funktioniert sehr gut. Obwohl ich ein Stück von @Rob 's Antwort in Bezug auf das Ende der Scrollview hinzugefügt habe. Zusammen scheint diese Lösung perfekt zu funktionieren.

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 

} 

Fügen Sie einfach Ihre minimumLineSpacing-kCellBaseWidth und voila.

+0

Ich liebe dich so sehr –

+0

Danke an alle, die dazu beigetragen haben. NUR EINE ERINNERUNG: Wenn Sie in Ihrem FlowLayout einen 'minimumLineSpacing' verwenden, müssen Sie diesen Wert berücksichtigen. Fügen Sie es zu kCellBaseWidth hinzu. – Womble

Verwandte Themen