UIScrollView自定义分页

时间:2011-08-04 17:33:52

标签: ios iphone objective-c uiview uiscrollview

我的问题与我尝试使用滚动条进行的自定义分页形式有关,如果您首先考虑在老虎机中实现的滚动视图类型,这更容易可视化。

所以说我的UIScrollView的宽度为100像素。假设它包含3个内部视图,每个视图的宽度为30个像素,使得它们以3个像素的宽度分隔。我想要实现的分页类型是每个页面是我的一个视图(30个像素),而不是滚动视图的整个宽度。

我知道通常,如果视图占用了滚动视图的整个宽度,并且启用了分页,那么一切正常。但是,在我的自定义分页中,我还希望滚动视图中的周围视图也可见。

我该怎么做?

7 个答案:

答案 0 :(得分:26)

我刚刚为另一个项目做了这个。您需要做的是将UIScrollView放入UIView的自定义实现中。我为此创建了一个名为ExtendedHitAreaViewController的类。 ExtendedHitAreaView重写hitTest函数以返回它的第一个子对象,它将是你的滚动视图。

您的滚动视图应该是您想要的页面大小,即30px with clipsToBounds = NO。 扩展命中区域视图应该是您想要显示的区域的完整大小,其中clipsToBounds = YES。

将滚动视图作为子视图添加到扩展命中区域视图,然后将扩展命中区域视图添加到viewcontroller的视图中。

@implementation ExtendedHitAreaViewContainer

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if ([self pointInside:point withEvent:event]) {
        if ([[self subviews] count] > 0) {
            //force return of first child, if exists
            return [[self subviews] objectAtIndex:0];
        } else {
            return self;
        }
    }
    return nil;
}
@end

答案 1 :(得分:12)

从iOS 5开始,有这种委托方法:- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

所以你可以这样做:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint
*)targetContentOffset {
    if (scrollView == self.scrollView) {
        CGFloat x = targetContentOffset->x;
        x = roundf(x / 30.0f) * 30.0f;
        targetContentOffset->x = x;
    } 
}

对于更高的速度,如果你想要更加敏捷的感觉,你可能想要调整一下targetContentOffset。

答案 2 :(得分:2)

我遇到了同样的问题,这对我很有用,在iOS 8上测试过。

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                 withVelocity:(CGPoint)velocity
          targetContentOffset:(inout CGPoint *)targetContentOffset
{
    NSInteger index = lrint(targetContentOffset->x/_pageWidth);
    NSInteger currentPage = lrint(scrollView.contentOffset.x/_pageWidth);

    if(index == currentPage) {
        if(velocity.x > 0)
            index++;
        else if(velocity.x < 0)
            index--;
    }

    targetContentOffset->x = index * _pageWidth;
}

如果速度不为零,我必须检查速度并始终转到下一页/上一页,否则在进行非常短而快速的滑动时会产生非动画跳跃。

更新:这似乎工作得更好:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                 withVelocity:(CGPoint)velocity
          targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGFloat index = targetContentOffset->x/channelScrollWidth;

    if(velocity.x > 0)
        index = ceil(index);
    else if(velocity.x < 0)
        index = floor(index);
    else
        index = round(index);

    targetContentOffset->x = index * channelScrollWidth;
}

这些用于横向滚动视图,使用y而不是x作为垂直滚动视图。

答案 3 :(得分:1)

我在滚动视图中有许多不同的视图,包括按钮和手势识别器。

@ picciano的答案不起作用(滚动效果很好,但按钮和识别器没有触及)对我来说,所以我找到了这个解决方案:

class ExtendedHitAreaView : UIScrollView {
    // Your insets
    var hitAreaEdgeInset = UIEdgeInsets(top: 0, left: -20, bottom: 0, right: -20) 

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        let hitBounds = UIEdgeInsetsInsetRect(bounds, hitAreaEdgeInset)
        return CGRectContainsPoint(hitBounds, point)
    }
}

答案 4 :(得分:1)

我一直在努力克服这个问题,并且找到了一个几乎完美的解决方案,该解决方案非常理想,可以满足您想要的页面宽度。

我将UIScrollView中的 scrollView.isPaging 设置为false(同时默认情况下为false),并将其委托设置为 UIScrollViewDelegate

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

    // Stop scrollView sliding:
    targetContentOffset.pointee = scrollView.contentOffset

    if scrollView == scrollView {
        let maxIndex = slides.count - 1
        let targetX: CGFloat = scrollView.contentOffset.x + velocity.x * 60.0
        var targetIndex = Int(round(Double(targetX / (pageWidth + spacingWidth))))
        var additionalWidth: CGFloat = 0
        var isOverScrolled = false

        if targetIndex <= 0 {
            targetIndex = 0
        } else {
            // in case you want to make page to center of View
            // by substract width with this additionalWidth
            additionalWidth = 20
        }

        if targetIndex > maxIndex {
            targetIndex = maxIndex
            isOverScrolled = true
        }

        let velocityX = velocity.x
        var newOffset = CGPoint(x: (CGFloat(targetIndex) * (self.pageWidth + self.spacingWidth)) - additionalWidth, y: 0)
        if velocityX == 0 {
            // when velocityX is 0, the jumping animation will occured
            // if we don't set targetContentOffset.pointee to new offset
            if !isOverScrolled &&  targetIndex == maxIndex {
                newOffset.x = scrollView.contentSize.width - scrollView.frame.width
            }
            targetContentOffset.pointee = newOffset
        }

        // Damping equal 1 => no oscillations => decay animation:
        UIView.animate(
            withDuration: 0.3, delay: 0,
            usingSpringWithDamping: 1,
            initialSpringVelocity: velocityX,
            options: .allowUserInteraction,
            animations: {
                scrollView.contentOffset = newOffset
                scrollView.layoutIfNeeded()
        }, completion: nil)
    }
}

幻灯片包含您插入到UIScrollView中的所有页面视图。

答案 5 :(得分:0)

经过几天的研究和排查,我想出了一些对我有用的东西!

首先,您需要子类化scrollview所在的视图,并使用以下方法覆盖此方法:

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

然后所有的魔法都发生在scrollview委托方法

    -(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
            //_lastOffset is declared in the header file
            //@property (nonatomic) CGPoint lastOffset;
            _lastOffset = scrollView.contentOffset;
     }

    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {

            CGPoint currentOffset = scrollView.contentOffset;
            CGPoint newOffset = CGPointZero;

            if (_lastOffset.x < currentOffset.x) {
                // right to left
                newOffset.x = _lastOffset.x + 298;
            }
            else {
                 // left to right
                 newOffset.x = _lastOffset.x - 298;
            }

            [UIView beginAnimations:nil context:NULL];
            [UIView setAnimationDuration:0.5];
            [UIView setAnimationDelay:0.0f];

            targetContentOffset->x = newOffset.x;
            [UIView commitAnimations];

      }

您还需要设置scrollview的减速率。我在ViewDidLoad

中做到了
    [self.scrollView setDecelerationRate:UIScrollViewDecelerationRateFast];

答案 6 :(得分:-1)

alcides'解决方案完美无缺。每当我进入scrollviewDidEndDragging和scrollViewWillEndDragging时,我只启用/禁用滚动视图的滚动。如果用户在分页动画完成之前滚动了几次,则单元格略微不对齐。

所以我有:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {

    scrollView.scrollEnabled = NO

    CGPoint currentOffset = scrollView.contentOffset;
    CGPoint newOffset = CGPointZero;

    if (_lastOffset.x < currentOffset.x) {
        // right to left
        newOffset.x = _lastOffset.x + 298;
    }
    else {
        // left to right
        newOffset.x = _lastOffset.x - 298;
    }

    [UIView animateWithDuration:0.4 animations:^{
        targetContentOffset.x = newOffset.x
    }];
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView {
    scrollView.scrollEnabled = YES
}