UICollectionView:页面控制器的当前索引路径

39

我正在使用CollectionView和流式布局来展示一系列单元格,同时还有一个页面控件来指示当前页数,但是好像没有办法获取当前索引路径,我知道可以获取可见单元格:

UICollectionView当前可见单元格索引

然而可能有多个可见单元格,即使每个单元格都占据屏幕的整个宽度,如果我将其滚动以使两个单元格各自占据了屏幕的一半,那么它们都是可见的,是否有一种方法只获取当前可见单元格的索引?

谢谢

11个回答

65

您可以通过在scrollViewDidScroll代理中监视contentOffset来获取当前索引。

它将是类似于这样的内容

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    NSInteger currentIndex = self.collectionView.contentOffset.x / self.collectionView.frame.size.width;

}

21
这很不错,但我发现将相同的代码放在“- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView”中会产生更平滑的效果,如果您需要更新UI。 - eunoia
9
我可以建议使用以下代码:NSInteger currentIndex = (NSInteger)(self.collectionView.contentOffset.x / self.collectionView.frame.size.width + 0.5);。当页面有半页或页面包含部分显示的集合视图单元时,这将提供更好的结果。 - hotdogsoup.nl
非常好用!特别是有了 @eunoia 的建议。谢谢! - Ivan Petrov

35

从视图中心通过NSIndexPath获取页面。

即使您的页面与UICollectionView的宽度不相等,也可以使用。

func scrollViewDidScroll(scrollView: UIScrollView) {
    let xPoint = scrollView.contentOffset.x + scrollView.frame.width / 2
    let yPoint = scrollView.frame.height / 2
    let center = CGPoint(x: xPoint, y: yPoint)
    if let ip = collectionView.indexPathForItemAtPoint(center) {
        self.pageControl.currentPage = ip.row
    }
}

2
不错的方法,我会在赋值之前添加一个从ip.rowpageControl.currentPage的相等性测试,以避免触发UIPageControl逻辑上的无用开销。 - dulgan

14

当滚动停止时,你肯定需要捕获可见的项目。使用以下代码来实现。

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    if let indexPath = myCollectionView.indexPathsForVisibleItems.first {
        myPageControl.currentPage = indexPath.row
    }
}

3
如果你的集合视图处于页面模式并且单元格宽度等于屏幕宽度(例如全屏的pageViewController),则此方法无法正常工作。得到的indexPath并不总是正确的。 - Rikco
2
@Rikco,你找到了解决方案吗?当页面模式启用且单元格宽度=屏幕宽度时,这正是我的情况。 - Roi Mulia

11

Swift 5.1

更易用且更安全,避免因空值而导致的崩溃。

func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if collectionView == newsCollectionView {
        if newsPager.currentPage == indexPath.row {
            guard let visible = newsCollectionView.visibleCells.first else { return }
            guard let index = newsCollectionView.indexPath(for: visible)?.row else { return }
            newsPager.currentPage = index
        }

    }
}

7
  1. Place PageControl in your view or set by Code.
  2. Set UIScrollViewDelegate
  3. In Collectionview-> cellForItemAtIndexPath (Method) add the below code for calculate the Number of pages,

    int pages = floor(ImageCollectionView.contentSize.width/ImageCollectionView.frame.size.width);
    [pageControl setNumberOfPages:pages];
    
  4. Add the ScrollView Delegate method,

    #pragma mark - UIScrollViewDelegate for UIPageControl
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
    {
        CGFloat pageWidth = ImageCollectionView.frame.size.width;
        float currentPage = ImageCollectionView.contentOffset.x / pageWidth;
    
        if (0.0f != fmodf(currentPage, 1.0f))
        {
            pageControl.currentPage = currentPage + 1;
        }
        else
        {
            pageControl.currentPage = currentPage;
        }
        NSLog(@"finishPage: %ld", (long)pageControl.currentPage);
    }
    

如果集合视图中有十个单元格,那么这将计算十次页面的数量?肯定只需要一次吧? - Max MacLeod
有没有更好的地方添加这段代码:int pages = floor(ImageCollectionView.contentSize.width/ImageCollectionView.frame.size.width); [pageControl setNumberOfPages:pages]; ??? - Abhishek Thapliyal

3
我曾经遇到过类似的情况,我的流式布局被设置为UICollectionViewScrollDirectionHorizontal,并且我正在使用页面控制器来显示当前页面。
我通过使用自定义流式布局实现了这一点
/------------------------ 自定义头文件(.h) ------------------------/
/**
* The customViewFlowLayoutDelegate protocol defines methods that let you coordinate with
*location of cell which is centered.
*/

@protocol CustomViewFlowLayoutDelegate <UICollectionViewDelegateFlowLayout>

/** Informs delegate about location of centered cell in grid.
*  Delegate should use this location 'indexPath' information to 
*   adjust it's conten associated with this cell. 
*   @param indexpath of cell in collection view which is centered.
*/

- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout cellCenteredAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface customViewFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, weak) id<CustomViewFlowLayoutDelegate> delegate;
@end

/------------------- 自定义头文件的实现文件(.m) -------------------/


这是用于自定义头文件的实现文件,文件格式为.m。
@implementation customViewFlowLayout
- (void)prepareLayout {
 [super prepareLayout];
 }

static const CGFloat ACTIVE_DISTANCE = 10.0f; //Distance of given cell from center of visible rect
 static const CGFloat ITEM_SIZE = 40.0f; // Width/Height of cell.

- (id)init {
    if (self = [super init]) {
    self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    self.minimumInteritemSpacing = 60.0f;
    self.sectionInset = UIEdgeInsetsZero;
    self.itemSize = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
    self.minimumLineSpacing = 0;
}
    return self;
    }

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return YES;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
   NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

CGRect visibleRect;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;

for (UICollectionViewLayoutAttributes *attribute in attributes) {
    if (CGRectIntersectsRect(attribute.frame, rect)) {

        CGFloat distance = CGRectGetMidX(visibleRect) - attribute.center.x;
        // Make sure given cell is center
        if (ABS(distance) < ACTIVE_DISTANCE) {
            [self.delegate collectionView:self.collectionView layout:self cellCenteredAtIndexPath:attribute.indexPath];
        }
    }
}
return attributes;
}

您的包含集合视图的类必须符合我前面在自定义布局头文件中描述的“CustomViewFlowLayoutDelegate”协议。例如:

@interface MyCollectionViewController () <UICollectionViewDataSource, UICollectionViewDelegate, CustomViewFlowLayoutDelegate>
@property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
@property (strong, nonatomic) IBOutlet UIPageControl *pageControl;
....
....
@end

将自定义布局与集合视图连接的方法有两种,一种是在xib中实现,另一种是通过代码实现,比如在viewDidLoad中:

customViewFlowLayout *flowLayout = [[customViewFlowLayout alloc]init];
flowLayout.delegate = self;
self.collectionView.collectionViewLayout = flowLayout;
self.collectionView.pagingEnabled = YES; //Matching your situation probably?

最后一件事,在MyCollectionViewController实现文件中,实现“CustomViewFlowLayoutDelegate”的代理方法。
- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout cellCenteredAtIndexPath:(NSIndexPath *)indexPath {
self.pageControl.currentPage = indexPath.row;

希望这对你有所帮助。 :)


2

注意 - 我发现 andykkt最初的回答很有用,但因为它是用 obj-c 编写的,所以我将其转换为 swift,并在另一个UIScrollView代理中实现逻辑,以获得更加平滑的效果。

func updatePageNumber() {
    // If not case to `Int` will give an error.
    let currentPage = Int(ceil(scrollView.contentOffset.x / scrollView.frame.size.width))
    pageControl.currentPage = currentPage
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    // This will be call when you scrolls it manually.
    updatePageNumber()
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    // This will be call when you scrolls it programmatically.
    updatePageNumber()
}

如果启用了分页,这将无法给出正确的答案。 - anoop4real

1

适用于 Swift 4.2

@IBOutlet weak var mPageControl: UIPageControl!
@IBOutlet weak var mCollectionSlider: UICollectionView!

private var _currentIndex = 0
private var T1:Timer!
private var _indexPath:IndexPath = [0,0]

private func _GenerateNextPage(){
    self._currentIndex = mCollectionSlider.indexPathForItem(at: CGPoint.init(x: CGRect.init(origin: mCollectionSlider.contentOffset, size: mCollectionSlider.bounds.size).midX, y: CGRect.init(origin: mCollectionSlider.contentOffset, size: mCollectionSlider.bounds.size).midY))?.item ?? 0
    self.mPageControl.currentPage = self._currentIndex
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    _SetTimer(AutoScrollInterval)
    _GenerateNextPage()
}

@objc private func _AutoScroll(){
    self._indexPath = IndexPath.init(item: self._currentIndex+1, section: 0)
    if !(self._indexPath.item < self.numberOfItems){
        _indexPath = [0,0]
    }
    self.mCollectionSlider.scrollToItem(at: self._indexPath, at: .centeredHorizontally, animated: true)
}
private func _SetTimer(_ interval:TimeInterval){
    if T1 == nil{
        T1 = Timer.scheduledTimer(timeInterval: interval , target:self , selector: #selector(_AutoScroll), userInfo: nil, repeats: true)
    }
}

你可以跳过函数 _SetTimer(),它是用于自动滚动的。

1
使用UICollectionViewDelegate方法
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    pageControl.currentPage = indexPath.row
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if pageControl.currentPage == indexPath.row {
        pageControl.currentPage = collectionView.indexPath(for: collectionView.visibleCells.first!)!.row
    }
}

1

Swift 5.0


 extension youriewControllerName:UIScrollViewDelegate{
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    
            let pageWidth = self.collectionView.frame.size.width
            pageControl.currentPage = Int(self.collectionView.contentOffset.x / pageWidth)
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接