AliSoftware/Reusable

CollectionView's dequeueReusableSupplementaryView(ofKind:for:viewType) not available

amaurydavid opened this issue · 4 comments

Hello :)
I'm trying to use the UICollectionView's dequeueReusableSupplementaryView(ofKind:for:viewType) function but I struggle with the viewType parameter.

From the function definition, the viewType parameter must be a UICollectionReusableView, and also a Reusable.

This first snippet compiles correctly:

let headerType = TitleHeaderView.self
      let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath, viewType: headerType)

But this next one doesn't compiles and gives me a Argument 'viewType' must precede argument 'for' error.

//This func is responsible for returning the correct header class according to the given indexPath
func retrieveHeaderType() -> (UICollectionReusableView & Reusable).Type { ... }

let headerType = retrieveHeaderType()
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath, viewType: headerType)

As I understand it, this error comes up because my headerType doesn't have the func's required type, and the compilator think I want to use the classic dequeue func.
But isn't a UICollectionReusableView & Reusable a correct type for dequeueReusableSupplementaryView(ofKind:for:viewType) ?
What type should my headerType func return in order to work?

Reusable version: 4.0.2 (imported with cocoapods)
Xcode 9.3
App project in Swift 4.1
Reusable project in Swift 3.3

Hello

I see two potential reasons for the error you see

  1. You pass viewType: headerType but headerType is a function returning your type, and you didn't call it. Didn't you want to use viewType: headerType() instead, to call the function so that it returns the expected (UICollectionReusableView & Reusable).Type?

  2. Sadly I'm not sure if the current version of Swift is capable of going to such levels of abstraction just yet. Tbh I'm actually quite surprised that (UICollectionReusableView & Reusable).Type would compile (meaning that Swift would somehow reference a metatype built dynamically from two protocols? reminds me of similar limitations of current version of Swift like not being able to make a meta type conform to a protocol, and other limitations like the ones forcing us to use type erasure, etc)

I think if you make your headerType() function's return type be concrete type instead of (UICollectionReusableView & Reusable).Type it would work. e.g. if you have class CommonRV: UICollectionReusableView, Reusable {} then use class MyRV1: CommonRV & class MyRV2: CommonRV in your code, you could have func headerType() -> CommonRV.Type { /* return MyRV1.Type or MyRV2.Type depending on some condition */ } and I think in that case it would work.

tl;dr I think what make Swift compiler unhappy here is that (UICollectionReusableView & Reusable).Type is maybe a little too complex for it to grasp, being a meta-type of a combination of a class and a protocol, not sure it's able to handle that just yet.

If that test confirms my hypothesis n°2, then your solution is either to do what I described above (make all your concrete classes of UICollectionReusableView that you want to use with Reusable be a subclass of class CommonRV: UICollectionReusableView, Reusable and not subclasses of UICollectionReusableView directly), or if you don't want to use inheritance, maybe we could find a workaround solution using type erasure? I'm not sure

The first potential reason you point is in fact a typo. In my example, the var and the func have the same name, but it's not the case in my code. I'll update my post to better reflect that.

About your second potential reason, I've tried to simplify like that:

typealias MyHeader = UICollectionReusableView & Reusable

//This func is responsible for returning the correct header class according to the given indexPath
func retrieveHeaderType() -> MyHeader.Type { ... }

But I've got the same issue as the typealias doesn't change anything from the compiler pov.

I've also thought about having all my headers inherit from MyHeader but I wonder if it will correctly dequeue a MyHeader or its subclass... I'll try.

if you have class CommonRV: UICollectionReusableView, Reusable {} then use class MyRV1: CommonRV & class MyRV2: CommonRV in your code, you could have func headerType() -> CommonRV.Type { /* return MyRV1.Type or MyRV2.Type depending on some condition */ } and I think in that case it would work.

It indeed compiles and works as expected :). Thanks!

Thanks for the follow up!
(And I hope a future version of Swift will allow such composed metatypes construct!)