[ios] UICollectionView current visible cell index

I am using UICollectionView first time in my iPad application. I have set UICollectionView such that its size and cell size is same, means only once cell is displayed at a time.

Problem: Now when user scroll UICollectionView I need to know which cell is visible I have to update other UI elements on change. I didn't find any delegate method for this. How can I achieve this?

Code:

[self.mainImageCollection setTag:MAIN_IMAGE_COLLECTION_VIEW];
[self.mainImageCollection registerClass:[InspirationMainImageCollectionCell class] forCellWithReuseIdentifier:@"cellIdentifier"];
[self.mainImageFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.mainImageFlowLayout setMinimumInteritemSpacing:0.0f];
[self.mainImageFlowLayout setMinimumLineSpacing:0.0f];
self.mainImageFlowLayout.minimumLineSpacing = 0;
[self.mainImageCollection setPagingEnabled:YES];
[self.mainImageCollection setShowsHorizontalScrollIndicator:NO];
[self.mainImageCollection setCollectionViewLayout:self.mainImageFlowLayout];

What I have tried:

As UICollectionView conforms to UIScrollView, I got when user scroll ends with UIScrollViewDelegate method

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

But inside above function how can I get current visible cell index of UICollectionView ?

This question is related to ios objective-c ipad uicollectionview

The answer is


converting @Anthony's answer to Swift 3.0 worked perfectly for me:

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    var visibleRect = CGRect()
    visibleRect.origin = yourCollectionView.contentOffset
    visibleRect.size = yourCollectionView.bounds.size
    let visiblePoint = CGPoint(x: CGFloat(visibleRect.midX), y: CGFloat(visibleRect.midY))
    let visibleIndexPath: IndexPath? = yourCollectionView.indexPathForItem(at: visiblePoint)
    print("Visible cell's index is : \(visibleIndexPath?.row)!")
}

Swift 3.0

Simplest solution which will give you indexPath for visible cells..

yourCollectionView.indexPathsForVisibleItems 

will return the array of indexpath.

Just take the first object from array like this.

yourCollectionView.indexPathsForVisibleItems.first

I guess it should work fine with Objective - C as well.


For completeness sake, this is the method that ended up working for me. It was a combination of @Anthony & @iAn's methods.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
      CGRect visibleRect = (CGRect){.origin = self.collectionView.contentOffset, .size = self.collectionView.bounds.size};
      CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
      NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:visiblePoint];
      NSLog(@"%@",visibleIndexPath);
}

UICollectionView current visible cell index: Swift 3, 4 and 5+

var visibleCurrentCellIndexPath: IndexPath? {
    for cell in self.collectionView.visibleCells {
        let indexPath = self.collectionView.indexPath(for: cell)
        return indexPath
     }
        
     return nil
}

As an Extension:

extension UICollectionView {
  var visibleCurrentCellIndexPath: IndexPath? {
    for cell in self.visibleCells {
      let indexPath = self.indexPath(for: cell)
      return indexPath
    }
    
    return nil
  }
}

Usage:

if let indexPath = collectionView.visibleCurrentCellIndexPath { 
   /// do something
}

indexPathsForVisibleItems might work for most situations, but sometimes it returns an array with more than one index path and it can be tricky figuring out the one you want. In those situations, you can do something like this:

CGRect visibleRect = (CGRect){.origin = self.collectionView.contentOffset, .size = self.collectionView.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:visiblePoint];

This works especially well when each item in your collection view takes up the whole screen.

Swift version

let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)

Also check this snippet

let isCellVisible = collectionView.visibleCells.map { collectionView.indexPath(for: $0) }.contains(inspectingIndexPath)

try this, it works. (in the example below i have 3 cells for example.)

    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: self.collectionView.contentOffset, size: self.collectionView.bounds.size)
    let visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect))
    let visibleIndexPath = self.collectionView.indexPathForItemAtPoint(visiblePoint)
    if let v = visibleIndexPath {
        switch v.item {
        case 0: setImageDescription()
            break
        case 1: setImageConditions()
            break
        case 2: setImageResults()
            break
        default: break
        }
    }

You can use scrollViewDidEndDecelerating: for this

//@property (strong, nonatomic) IBOutlet UICollectionView *collectionView;

   - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

        for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {
            NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
            NSUInteger lastIndex = [indexPath indexAtPosition:[indexPath length] - 1];
            NSLog(@"visible cell value %d",lastIndex);
        }

    }

Just want to add for others : for some reason, I didnt not get the cell that was visible to the user when I was scrolling to previous cell in collectionView with pagingEnabled.

So I insert the code inside dispatch_async to give it some "air" and this works for me.

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    dispatch_async(dispatch_get_main_queue(), ^{
            UICollectionViewCell * visibleCell= [[self.collectionView visibleCells] objectAtIndex:0];


            [visibleCell doSomthing];
        });
}

Swift 5:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    var visibleRect = CGRect()

    visibleRect.origin = collectionView.contentOffset
    visibleRect.size = collectionView.bounds.size

    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)

    guard let indexPath = collectionView.indexPathForItem(at: visiblePoint) else { return } 

    print(indexPath)
}

Working Answers Combined In Swift 2.2 :

 func scrollViewDidEndDecelerating(scrollView: UIScrollView) {

        var visibleRect = CGRect()

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

        let visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect))

        let visibleIndexPath: NSIndexPath = self.collectionView.indexPathForItemAtPoint(visiblePoint)

        guard let indexPath = visibleIndexPath else { return } 
        print(indexPath)

    }

It will probably be best to use UICollectionViewDelegate methods: (Swift 3)

// Called before the cell is displayed    
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

// Called when the cell is displayed
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

For Swift 3.0

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: colView.contentOffset, size: colView.bounds.size)
    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
    let indexPath = colView.indexPathForItem(at: visiblePoint)
}

This is old question but in my case...

- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    _m_offsetIdx = [m_cv indexPathForCell:m_cv.visibleCells.firstObject].row;
}

- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _m_offsetIdx = [m_cv indexPathForCell:m_cv.visibleCells.lastObject].row;
}

In this thread, There are so many solutions that work fine if cell takes full screen but they use collection view bounds and midpoints of Visible rect However there is a simple solution to this problem

    DispatchQueue.main.async {
        let visibleCell = self.collImages.visibleCells.first
        print(self.collImages.indexPath(for: visibleCell))
    }

by this, you can get indexPath of the visible cell. I have added DispatchQueue because when you swipe faster and if for a brief moment the next cell is shown then without dispactchQueue you'll get indexPath of briefly shown cell not the cell that is being displayed on the screen.


Swift 3 & Swift 4:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
   var visibleRect = CGRect()

   visibleRect.origin = collectionView.contentOffset
   visibleRect.size = collectionView.bounds.size

   let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)

   guard let indexPath = collectionView.indexPathForItem(at: visiblePoint) else { return } 

   print(indexPath[1])
}

If you want to show actual number than you can add +1


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to objective-c

Adding a UISegmentedControl to UITableView Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Warp \ bend effect on a UIView? Use NSInteger as array index Detect if the device is iPhone X Linker Command failed with exit code 1 (use -v to see invocation), Xcode 8, Swift 3 ITSAppUsesNonExemptEncryption export compliance while internal testing? How to enable back/left swipe gesture in UINavigationController after setting leftBarButtonItem? Change status bar text color to light in iOS 9 with Objective-C

Examples related to ipad

What is correct media query for IPad Pro? iPad Multitasking support requires these orientations How to restrict UITextField to take only numbers in Swift? How to present a modal atop the current view in Swift Presenting a UIAlertController properly on an iPad using iOS 8 Detect current device with UI_USER_INTERFACE_IDIOM() in Swift HTML5 Video tag not working in Safari , iPhone and iPad Install .ipa to iPad with or without iTunes How to hide UINavigationBar 1px bottom line How to fix UITableView separator on iOS 7?

Examples related to uicollectionview

How to set UICollectionViewCell Width and Height programmatically How to make a simple collection view with Swift How to set cell spacing and UICollectionView - UICollectionViewFlowLayout size ratio? UICollectionView - dynamic cell height? UICollectionView Self Sizing Cells with Auto Layout Fatal error: unexpectedly found nil while unwrapping an Optional values Paging UICollectionView by cells, not screen UICollectionView - Horizontal scroll, horizontal layout? UICollectionView current visible cell index Creating a UICollectionView programmatically