UIScrollView自定义分页大小

use*_*625 41 iphone objective-c uiscrollview ipad

在UIScrollView中进行分页是一个很棒的功能,我需要的是将分页设置为较小的距离,例如我希望我的UIScrollView页面的大小小于UIScrollView的帧宽.谢谢

luc*_*ius 68

有一个UIScrollView你可以使用委托方法.将您的类设置为滚动视图的委托,然后实现以下内容:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGFloat kMaxIndex = 23;
    CGFloat targetX = scrollView.contentOffset.x + velocity.x * 60.0;
    CGFloat targetIndex = 0.0;
    if (velocity.x > 0) {
        targetIndex = ceil(targetX / (kCellWidth + kCellSpacing));
    } else if (velocity.x == 0) {
        targetIndex = round(targetX / (kCellWidth + kCellSpacing));
    } else if (velocity.x < 0) {
        targetIndex = floor(targetX / (kCellWidth + kCellSpacing));
    }
    if (targetIndex < 0)
        targetIndex = 0;
    if (targetIndex > kMaxIndex)
        targetIndex = kMaxIndex;
    targetContentOffset->x = targetIndex * (kCellWidth + kCellSpacing);
    //scrollView.decelerationRate = UIScrollViewDecelerationRateFast;//uncomment this for faster paging
}
Run Code Online (Sandbox Code Playgroud)

速度参数是必要的,以确保滚动感觉自然,并且当手指仍在移动时触摸结束时不会突然结束.单元格宽度和单元格间距是页面宽度和视图中页面之间的间距.在这种情况下,我正在使用UICollectionView.

  • 如果你想在进行非常小的滑动时避免滚动视图跳到其初始位置,你可以使用:`if(velocity.x> 0){targetIndex = ceil(targetX /(self.cardViewWidth + kCardSpacing)); } else {targetIndex = floor(targetX /(self.cardViewWidth + kCardSpacing)); }` (16认同)
  • 为什么targetX表达式最后乘以60?这是我无法理解的唯一部分.这个神奇的数字来自哪里? (12认同)
  • 不要忘记使用`scrollView.decelerationRate`来获得你想要的感觉 - 我必须把它设置为`UIScrollViewDecelerationRateFast`才感觉自然 (6认同)
  • @lucious,我已经弄明白了:我必须将scrollView.pagingEnabled设置为NO :) (5认同)
  • 这是太棒了 ! (2认同)
  • @KoCMoHaBTa根据文档“速度以点/毫秒为单位”,因此,通过添加滚动视图在接下来的60ms内行进的距离,可以将拖动力考虑在内。更强大的阻力将进一步滚动。 (2认同)

Fra*_*scu 22

  1. 将scrollView大小更改为所需的页面大小
  2. 设置你的 scroll.clipsToBounds = NO
  3. 创建一个UIView子类(例如HackClipView)并覆盖hitTest:withEvent:方法

    -(UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *)event
    {     
        UIView* child = [super hitTest:point withEvent:event]; 
        if (child == self && self.subviews.count > 0)  
        {
            return self.subviews[0];
        }
        return child;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 设置 HackClipView.clipsToBounds = YES

  5. 将您的scrollView放在此HackClipView中(具有您想要的总滚动大小)

有关详细信息,请参阅 此答案

更新: 如lucius回答中所述,您现在可以实现UIScollViewDelegate协议并使用该- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset方法.因为它targetContentOffset是一个指针.使用此方法不能保证滚动视图页面的结果相同,因为用户可以一次滚动浏览多个页面.但设置descelerationRatefast差点给你同样的结果

  • 这是一个糟糕的答案.如果使滚动视图变小,则可触摸区域也会缩小到您设置的帧.这个问题专门询问如何处理"小于UIScrollView框架宽度的页面". (4认同)
  • @Joshua,不,通过覆盖superview上的hitTest方法,您可以重定向scrollView子项上的触摸. (3认同)

aim*_*ess 11

您应该禁用分页并将UIPanGestureRecognizer添加到滚动视图并自己处理分页.

- (void)viewDidLoad {

    [super viewDidLoad];
    CGRect viewRect = self.view.bounds; // View controller's view bounds
    theScrollView = [[UIScrollView alloc] initWithFrame:viewRect]; 

    theScrollView.scrollsToTop      = NO;
    theScrollView.pagingEnabled         = NO;
    theScrollView.delaysContentTouches  = NO;
    theScrollView.delegate = self;

    [self.view addSubview:theScrollView];

    UIPanGestureRecognizer * peter = [[[UIPanGestureRecognizer alloc] initWithTarget:self  
                                                                              action:@selector(handlePan:)]
                                       autorelease]; 
    [theScrollView addGestureRecognizer:peter]; 

}

-(void)handlePan:(UIPanGestureRecognizer*)recognizer{

switch (recognizer.state) {
    case UIGestureRecognizerStateBegan:{
        // panStart and startPoint are instance vars for the viewContainer 
        panStart = theScrollView.contentOffset;
        startPoint = [recognizer locationInView:theScrollView]; 


        break;
    }
    case UIGestureRecognizerStateChanged:{

        CGPoint newPoint = [recognizer locationInView:theScrollView];
        CGFloat delta = startPoint.x - newPoint.x;
        if ( abs(delta) > 2)
            theScrollView.contentOffset = CGPointMake( theScrollView.contentOffset.x + delta, 0); 

        CGFloat moveDelta = panStart.x - theScrollView.contentOffset.x;                               


        // current witdh should hold the currently displayed page/view in theScrollView
        if ( abs(moveDelta) > (currentWidth * 0.40)){
            panStart = theScrollView.contentOffset;
            startPoint = newPoint;

            //NSLog(@"delta is bigger"); 
            if ( moveDelta < 0 )
                [self incrementPageNumber]; // you should implement this method and present the next view
            else 
                [self decrementPageNumber]; // you should implement this method and present the previous view

            recognizer.enabled = NO; // disable further event until view change finish

        }

        break; 
    }

    case UIGestureRecognizerStateEnded:
    case UIGestureRecognizerStateCancelled:

        recognizer.enabled = YES; 
        [self showDocumentPage:currentPage]; 

        break;


    default:
        break;
}
Run Code Online (Sandbox Code Playgroud)

}

  • 在大多数情况下,非常糟糕的主意 (3认同)

Ant*_*ich 6

简化重用的 Swift 4.1 解决方案:

/// Protocol that simplifies custom page size configuration for UIScrollView.
/// Sadly, can not be done better due to protocol extensions limitations - /sf/ask/2764101791/
/// - note: Set `.decelerationRate` to `UIScrollViewDecelerationRateFast` for a fancy scrolling animation.
protocol ScrollViewCustomHorizontalPageSize: UIScrollViewDelegate {
    /// Custom page size
    var pageSize: CGFloat { get }

    /// Helper method to get current page fraction
    func getCurrentPage(scrollView: UIScrollView) -> CGFloat

    /// Helper method to get targetContentOffset. Usage:
    ///
    ///     func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    ///         targetContentOffset.pointee.x = getTargetContentOffset(scrollView: scrollView, velocity: velocity)
    ///     }
    func getTargetContentOffset(scrollView: UIScrollView, velocity: CGPoint) -> CGFloat

    /// Must be implemented. See `getTargetContentOffset` for more info.
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
}

extension ScrollViewCustomHorizontalPageSize {
    func getCurrentPage(scrollView: UIScrollView) -> CGFloat {
        return (scrollView.contentOffset.x + scrollView.contentInset.left) / pageSize
    }

    func getTargetContentOffset(scrollView: UIScrollView, velocity: CGPoint) -> CGFloat {
        let targetX: CGFloat = scrollView.contentOffset.x + velocity.x * 60.0

        var targetIndex = (targetX + scrollView.contentInset.left) / pageSize
        let maxOffsetX = scrollView.contentSize.width - scrollView.bounds.width + scrollView.contentInset.right
        let maxIndex = (maxOffsetX + scrollView.contentInset.left) / pageSize
        if velocity.x > 0 {
            targetIndex = ceil(targetIndex)
        } else if velocity.x < 0 {
            targetIndex = floor(targetIndex)
        } else {
            let (maxFloorIndex, lastInterval) = modf(maxIndex)
            if targetIndex > maxFloorIndex {
                if targetIndex >= lastInterval / 2 + maxFloorIndex {
                    targetIndex = maxIndex
                } else {
                    targetIndex = maxFloorIndex
                }
            } else {
                targetIndex = round(targetIndex)
            }
        }

        if targetIndex < 0 {
            targetIndex = 0
        }

        var offsetX = targetIndex * pageSize - scrollView.contentInset.left
        offsetX = min(offsetX, maxOffsetX)

        return offsetX
    }
}
Run Code Online (Sandbox Code Playgroud)

只需符合ScrollViewCustomPageSizeUIScrollView/UITableView/UICollectionView 委托中的协议即可,例如:

extension MyCollectionViewController: ScrollViewCustomPageSize {
    var pageSize: CGFloat {
        return 200
    }

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        targetContentOffset.pointee.x = getTargetContentOffset(scrollView: scrollView, velocity: velocity)
    }
}
Run Code Online (Sandbox Code Playgroud)

对于花哨的滚动,我还建议设置 collectionView.decelerationRate = UIScrollViewDecelerationRateFast


His*_*erg 1

设置contentOffsetin -(void)scrollViewDidScroll:(UIScrollView *)scrollView方法。

另请参阅UIScrollViewDelegate 参考资料