How to bind UIScrollView to icons (e.g. App Store: Feature)

What I want to get is the same behavior as in this scroll view:

App Store feature scroll view (left)

I know this uses HTML, not its own API, but I'm trying to implement it as a component of UIKit.

Now, to the behavior I'm looking for:

  • Note that this is a scrollable scroll view, but the "page size" is smaller than the width of the view.
  • When you scroll it from left to right, each page "snaps" to the leftmost element.
  • When you scroll it from the right edge to the left, it “snaps” to the rightmost element.

On the same page, but now from right to left:

App Store feature scroll view (right)

What I tried:

  • , -, hitTest, .
  • scrollViewWillEndDragging: withVelocity: targetContentOffset: targetContentOffset , , .
  • scrollViewDidEndDecelerating: , , , .
  • scrollViewDidEndDragging: willDecelerate: , "" .

.

!

Update:

Rob Mayoff, . , , 0, , , .

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

, "". "".

+5
4

scrollView.decelerationRate = UIScrollViewDecelerationRateFast, scrollViewWillEndDragging:withVelocity:targetContentOffset:, , , .

-, :

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

viewDidLoad, decelerationRate:

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

offsetStep , . 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);
}

baseOffset, X . viewDidAppear::

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

offsetStep. scrollViewWillEndDragging:withVelocity:targetContentOffset:. velocity baseOffset offsetStep. baseOffset 0 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;
}

, , targetContentOffset->x .

, . , . App Store.

, (targetContentOffset->x = baseOffset) :

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

.

git.

+14

pagingEnabled decelerationRate = UIScrollViewDecelerationRateFast scrollViewDidEndDragging scrollViewDidEndDecelerating ( , ). , , , , . animateWithDuration, .

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

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

snapScrollView, :

- (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;
                     }];
}
+12

, App Store, . kCellBaseWidth - .

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

Inclusion for Swift. @avdyushin's answer is by far the easiest and, as Ben said, works very well. Although I added a snippet from @Rob's answer about the end of the scroll. Together this solution works perfectly.

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

}

Just add minimumLineSpacingin kCellBaseWidthand voila.

+2
source

All Articles