Iglistkit: Retrieve frame of section/cell in collection view

Created on 4 Nov 2016  路  9Comments  路  Source: Instagram/IGListKit

I want to be able to retrieve the frame of a section in the collection view, however, I can't see a way to do that at the moment. Normally I would use the following code to retrieve the frame and check if it's "visible enough" so that I can play a video:

let attributes = collectionView.layoutAttributesForItem(at: indexPath)!
let cellRect = attributes.frame
let cellRectInSV = collectionView.convert(rect: cellRect, to: collectionView.superview)
let completelyVisible = collectionView.frame.contains(CGPoint(x: cellRectInSV.midX, y: CellRectInSV.midY))

With IGListKit I'm retrieving the indexPath like this:

let index = Int(adapter.section(for: sectionController))
let indexPath = IndexPath(row: 0, section: index)

But if the section has multiple cells then that index path will not be correct. Is there a better way to do this?

question

Most helpful comment

@manonthemoon42 We use the scrollViewDelegate to federate out scroll events to various handlers and delegates that perform different operations, one of them looking for video cells and calculating auto play.

We don't actually rely on section controllers at all for video playback though, instead we just grab the visibleCells and filter ones that conform to a video player protocol. Whichever is the most visible is told to play.

Doing the frame math is really really simple:

  • Get the collectionView.bounds
  • Take the cell.frame of each visible, video-playing cell
  • For each cell frame, find the % of the cell visible with

    • CGRectIntersection(cell.frame, collectionView.bounds).size.height / cell.frame.size.height

  • Whichever cell is most visible, play it

In fact, scrollViewDidScroll gets called too many times, and doing the operations below seems too much

What behavior were you seeing? Did you do some profiling to see what the stalls were?

All of our scroll view operations and stuff is done on the main thread, all using IGListKit, and maintaining 60fps. So its definitely possible!

Very curious about the bottlenecks you ran into.

All 9 comments

Hmm this could be easier w/ a helper method on IGListAdapter.

But I think you could just do:

let items = sectionController.numberOfItems()
let section = adapter.section(for: sectionController)
let first = IndexPath(item: 0, section: section)
let last = IndexPath(item: items - 1, section: section)
let firstFrame = collectionView.layoutAttributesForItem(at: first)!.frame
let lastFrame = collectionView.layoutAttributesForItem(at: last)!.frame
let sectionFrame = firstFrame.union(lastFrame)

Granted this doesn't include supplementary views or section insets.

Yep that works, cheers!

This seems like a good solution for this case, but note that for a standard UICollectionViewFlowLayout, the union of the first and last item frames within a section will not in general produce a region that intersects all item frames within the section. As an example, consider a 2-column grid with 3 items, the first 2 of which are in the first row. The union of first and last item frames produces the first column, which excludes the second item.

@rnystrom I'm trying to do similar behavior. Saw that you recommended more using IGListAdapter methods. Which API do you recommend the most for analyzing frames ?
visibleObjects, visibleSectionControllers ?

I really like the Instagram behavior.

  • if a cell appears with a video and it's the only one in the screen, then the video plays automatically.
  • if a cell appears with a video and the section above also contains a video cell, the video in the bottom won't play automatically. It will starts to play only if we scroll more until the bottom video cell will be more dominant than the currently playing video cell (the above one).

So far, the solution solution I had in mind is :

  • To listen callback of scrollViewDelegate inside the main view controller.
  • At any scrolling action, extracting the visibleSectionControllers from the adapter.
  • Get the visible cells for each visible section controller. (visibleCells for section controller, called using collectionContext reference on each sectionController).
  • Comparing the frame of the video cell, and play the video that is more dominant by its height.

I think that this solution would work. But I'd rather ask here if it's a good solution to go with. Want to make sure to implement the cleanest solution. I'm a little concerned about analyzing/comparing frames anytime the user scroll. Is it okay, or maybe too expensive ?

@rnystrom As follow up of my previous comment. I played around with my solution, and it's too expensive to make that solution in the main thread. In fact, scrollViewDidScroll gets called too many times, and doing the operations below seems too much :

  • Getting the section controllers
  • Getting the visible video cells from those section controllers
  • Comparing them to get the video cell that is the most dominant

It feels like the scroll experience gets weird now. I assume that it's too much for the main thread that s why it blocks it a little bit.
Then... I tried to make those operations in background thread, but it's not possible because some IGListKit APIs requires that they get called only in main thread.

Any suggestions how to implement Instagram like experience ? If Instagram is using IGListKit, I'm very interested on what was the approach to identify which video should be played if there are two videos appearing in the collection view.

@manonthemoon42 We use the scrollViewDelegate to federate out scroll events to various handlers and delegates that perform different operations, one of them looking for video cells and calculating auto play.

We don't actually rely on section controllers at all for video playback though, instead we just grab the visibleCells and filter ones that conform to a video player protocol. Whichever is the most visible is told to play.

Doing the frame math is really really simple:

  • Get the collectionView.bounds
  • Take the cell.frame of each visible, video-playing cell
  • For each cell frame, find the % of the cell visible with

    • CGRectIntersection(cell.frame, collectionView.bounds).size.height / cell.frame.size.height

  • Whichever cell is most visible, play it

In fact, scrollViewDidScroll gets called too many times, and doing the operations below seems too much

What behavior were you seeing? Did you do some profiling to see what the stalls were?

All of our scroll view operations and stuff is done on the main thread, all using IGListKit, and maintaining 60fps. So its definitely possible!

Very curious about the bottlenecks you ran into.

I didn't have a chance to do any profiling. I assumed directly that my approach was too expensive.
I over complicated my logic. I was first getting the section controllers, and then getting the visible cells from them. So I was making unnecessary iterations when I could get the visible cells directly from the collection view API.

I've implemented a solution based on your latest comment, and it works perfectly, it's really amazing.
Now, i'm getting the visible video cells by using array's filter method. Then getting the most visible cell by using array's method max.
From there, I play the video, and stop the already playing video(s).

Thank you @rnystrom for your response! I Really love 鉂わ笍 IGListKit 鉂わ笍 !!

@rnystrom , actually... I spoke a bit too fast about my scrolling issues ^^.
The scrolling was okay in simulator only. Then I tested in simulator, scrolled pretty fast, and there was some blocking UI effect. It gets blocks for a very short of time, but still provides a weird effect.

The frame calculations works very well, and doesn't impact the scrolling experience. So far, this piece works perfectly.

I think the reason why I have that problem, it's my usage of AVPlayer. I read online that creation of AVPlayerItem, or some operations like play, seek are executed in the main thread and consumes FPS. I checked using Time Profiler and AVPlayer gets executed in main thread even if it's wrapped into a Dispatch queue background. Screenshot below :
screen shot 2016-12-29 at 8 20 51 pm

To resolve that, the first thing I thought about, it's to create my AVPlayerItem and making the AVPlayer operations in the background thread, but it didn't resolves the issue. Figured online that all those actions are still executed in main thread no matter what internally. After investigation on Stackoverflow, Reddit, Google, seems like many other engineers faces the same issue and it seems very tricky to resolve. Many people recommends using AsyncDisplayKit to resolve that problem.

For Instagram, do you guys use directly AVFoundation, or like Facebook app, you guys also uses AsyncDisplayKit - ASVideoNode for the videos ?
In advance, I understand if it's an information that cannot be shared since it's not IGListKit related.

@manonthemoon42 ahhhhh ya, good ol AVFoundation.... I honestly don't think I can get too specific on our video playing techniques 馃檴 however it _is possible_ to still get super buttery smooth scrolling w/ AVFoundation. Just have to make sure you're not overplaying (maybe reuse players/views) and to download/preprocess as much data as possible beforehand.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alexwillrock picture alexwillrock  路  3Comments

rnystrom picture rnystrom  路  3Comments

FazeelAkhtar picture FazeelAkhtar  路  3Comments

jessesquires picture jessesquires  路  3Comments

runmad picture runmad  路  3Comments