tvOS support
I'm trying to add support for tvOS which is basically adding support for navigation via focus of elements. I've encountered a couple of things that I could use some help with. The first is that When I scroll down to the point where new rows are dequeued the layout goes into a loop of scrollViewDidScroll, invalidation and re-build of layout. If I comment out the code that changes the collectionView's bounds +- of 0.1 then this loop breaks. Another thing that worked was to add a guard in scrollViewDidScroll to check if the contentOffset was the same as before and then just return without invalidation of the layout. It seems that each change of the bounds also does some scrolling of the cells which causes the loop. The other issue is related to the first and is that the rows can't scroll horizontally, regardless if the bounds nudging is used or not. I've found no workaround for this.
Any help would be appreciated :-)
Oh, interesting. I've never tried tvOS development but I do have some ideas.
-
The bounds nudging is really an ugly hack and simply a fix for what I consider a bug in
UICollectionView. It's very probable thatUICollectionViewon tvOS works differently and might not have this problem at all (which is obvious since it enters the endless loop there). -
For the horizontal scrolling - yeah, I think tvOS does scroll in a different way since it doesn't really scroll on touch but more on a selection basis. I'm not sure this can be fixed by simply listening to scroll view events, this might require an entire different solution on tvOS.
In the end, my suggestion is probably not using this layout on tvOS. What tvOS version are you targeting? Could it be possible for you to instead use UICollectionViewCompositionalLayout which can solve the same problems that this project does? iOS/tvOS 13+ only though...
And also, it turns out that this UICollectionView-bug is fixed on iOS 13 (probably to support UICollectionViewCompositionalLayout 😏), so I can remove the hack when running on 13+! Thanks for making me aware of that
Thanks for the input, I think you're right that the scrolling to force dequeueing must come from a focus event somehow. I'll think a bit about this. Yeah, I'd love to use the iOS/tvOS 13 features since they are massively improving what can be done with high efficiency on UICollectionViews. But, the product I'm working on still needs to support 11 and 12 for some time to come so that's why I got all excited about this project since it has the potential to solve a whole lot of issues we are working around when using nested UICollectionViews (one vertical that contains a number of horisontal ones). I'll tinker some more with the tvOS support on this before I give up though :-) .
If you come up with something, let me know/PR me 😄 Would be nice if this supported tvOS as well.
Interestingly, I can't reproduce the "bug" where cells aren't dequeued on any iOS version now 🤔 so it seems like the hack with the bounds offset can be fully removed. Could you maybe see if that's the case for you too? @snoozemoose
Yep, I can confirm that. I just tested iOS 10 and 13 in simulator and both worked fine when I commented out adjustBoundsToInvalidateVisibleItemIndexPaths() and isAdjustingBoundsToInvalidateHorizontalSection. Strange but good I guess!
Is there a good way to get a reference to the scroll view from the ViewController's delegate method, like didUpdateFocus() where I get the cells that get/lose focus? I.e. can I traverse the cell's view hierarchy to find the scroll view? If I can then I could probably activate a little bit of scrolling of the scroll view when the first/last visible cell gets focus and perhaps that would trigger a cell dequeue.
Thanks for checking!
I have deliberately not exposed the scroll views, since they are decoration views that will be reused and are not related to the cell hierarchy, so the traversal would not work.
It would however be pretty easy to add a setHorizontalOffset:forSection: or similar - would that solve the problem?
Yeah, I think it could. I actually haven't yet figured out how the scroll view magic works :-) but I believe that if we could just force the scroll view to scroll the next, not yet visible, item into the bounds of the scroll view then it would get dequeue and displayed. Have I understood it correct? If so, then how about this second thought: can we extend the horizontal width of the bounds of the scroll view just enough to always fit the next, not yet visible, item and then force that item to scroll into visibility when it gets focus. Hope you understand what i mean :-).
I have been thinking some more about this, and I'm pretty certain this is the problem:
tvOS calls [collectionView scrollToItemAtIndexPath:atScrollPosition:animated:] whenever focus changes. This makes the collection view ask the layout "where is the frame of the item at this index path?" and it gets something outside the screen. It attempts to scroll to make that frame visible, but now we have our problem. This layout has frames that are outside the possible bounds of the collection view. The scrolling works vertically (I assume), but when the collection view attempts to scroll horizontally to find the cell, it can't, because its content size is only the width of the screen.
This is a big limitation of this layout - it does not work the way UICollectionView expects. The collection view expects all frames of items to be within scrollable bounds, but this layout instead updates the frames of items as users scrolls horizontally.
So in the end, scrollToItemAtIndexPath: has never worked correctly horizontally in this layout, and this is why it doesn't work on tvOS either. We have to fix the underlying problem and it should fix your problem as well.
Now, how do we fix this? UICollectionViewLayout has no idea when someone calls scrollToItemAtIndexPath: in the collection view. If it did, the fix would be simple - just offset that horizontal section so that the item is within the collection view bounds.
Right now I feel that the only solution is to swizzle this method on UICollectionView, intercept the call, and offset the section as needed.
Do you have any other ideas?
Hey, I added support for scrollToItemAtIndexPath: in a branch (linked above), could you test if that solves the problem? If not, I added a new public API to set the offset of a section which you can call from one of the focus method like you suggested above!
But probably what you should use is scrollToItemAtIndexPath:scrollPosition:animated: on the collection view itself
Hi! Sorry for the late reply, I had relatives visiting over the weekend :-). I believe you are right about the underlying cause and the proposed solution. I've put together a tvOS sample project, based on your example. At a quick test it looks promising in terms of "it moves horizontally" upon focus change but there was something with the destination position not getting to the right place. I will take a closer look at this hopefully tonight, otherwise tomorrow.