Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4 Cannot invoke 'index' with an argument list of type

I have a problem with calling the array method index(of:). MyClass inherits from UIViewController and conforms to the MyDelegate protocol.

//self.viewControllers: [(UIViewController & MyDelegate)]
guard let myController = viewController as? MyClass,
let index = self.viewControllers.index(of: myController) else {return}

then I get the error:

Cannot invoke 'index' with an argument list of type '(of: (UIViewController & MyDelegate))'

How can I solve this problem and is there a better solution than implementing index(of:) in an extension?

extension Array where Element == (UIViewController & MyDelegate) { 
    func index(of: Element) -> Int? { 
        for i in 0..<self.count { 
            if self[i] == of {
                return i
            } 
        } 
        return nil 
    } 
}
like image 641
John Reese Avatar asked Jun 11 '17 14:06

John Reese


1 Answers

This is almost certainly just an extension of the fact that protocols (aka. existentials) don't conform to themselves. So the class existential UIViewController & MyDelegate doesn't conform to Equatable, even though UIViewController does.

Therefore because index(of:) is constrained to being called on a Collection with Equatable elements, you cannot call it on a [UIViewController & MyDelegate].

Here's a more minimal example:

protocol P {}
protocol X {}
class Foo : P {}

func foo<T : P>(_ t: T) {}

func bar(_ f: Foo & X) {
  // error: Protocol type 'Foo & X' cannot conform to 'P' because only concrete
  // types can conform to protocols
  foo(f)
}

We cannot pass f as an argument to foo(_:) as Foo & X doesn't conform to P, even though Foo does. However really this should be a clear-cut case of where the existential should always be able to conform to itself, so I went ahead and filed a bug.

Until fixed, a simple solution is just to do an intermediate cast to the concrete type – so in our example, we can do:

foo(f as Foo)

and in your example, you can do:

let index = (self.viewControllers as [UIViewController]).index(of: myController) 
like image 114
Hamish Avatar answered Sep 30 '22 20:09

Hamish