I assume that the answer to this question will address issues with Objective-C protocols in general, but this is the first problem of this type that I've come across.
I expect for these methods to be used, when implementing UIPageViewControllerDataSourceWithConnections.
import UIKit
protocol UIPageViewControllerDataSourceWithConnections: UIPageViewControllerDataSource {
var connectedViewControllers: [UIViewController] {get}
}
extension UIPageViewControllerDataSourceWithConnections {
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController
) -> UIViewController? {return connectedViewController(
current: viewController,
adjustIndex: -
)}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController
) -> UIViewController? {return connectedViewController(
current: viewController,
adjustIndex: +
)}
private func connectedViewController(
current viewController: UIViewController,
adjustIndex: (Int, Int) -> Int
) -> UIViewController? {
let requestedIndex = adjustIndex(connectedViewControllers.indexOf(viewController)!, 1)
return connectedViewControllers.indices.contains(requestedIndex) ?
connectedViewControllers[requestedIndex] : nil
}
func presentationCountForPageViewController(pageViewController: UIPageViewController)
-> Int {return connectedViewControllers.count}
func presentationIndexForPageViewController(pageViewController: UIPageViewController)
-> Int {
return connectedViewControllers.indexOf(pageViewController.viewControllers!.first!)!
}
}
However, that won't compile. I have to implement this nonsense to make things work. Can you tell me why? Is a code-lighter solution available?
// connectedViewControllers is defined elsewhere in InstructionsPageViewController.
extension InstructionsPageViewController: UIPageViewControllerDataSourceWithConnections {
// (self as UIPageViewControllerDataSourceWithConnections) doesn't work.
// Workaround: use a different method name in the protocol
func pageViewController(pageViewController: UIPageViewController,
viewControllerBeforeViewController viewController: UIViewController
) -> UIViewController? {
return pageViewController(pageViewController,
viewControllerBeforeViewController: viewController
)
}
func pageViewController(pageViewController: UIPageViewController,
viewControllerAfterViewController viewController: UIViewController
) -> UIViewController? {
return pageViewController(pageViewController,
viewControllerAfterViewController: viewController
)
}
// (self as UIPageViewControllerDataSourceWithConnections)
// works for the optional methods.
func presentationCountForPageViewController(pageViewController: UIPageViewController)
-> Int {
return (self as UIPageViewControllerDataSourceWithConnections)
.presentationCountForPageViewController(pageViewController)
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController)
-> Int {
return (self as UIPageViewControllerDataSourceWithConnections)
.presentationIndexForPageViewController(pageViewController)
}
}
When you have a problem like this, where you're wondering about the limits of the Swift language itself, it helps to reduce it to a simpler version of the problem.
First, let's ask: is it possible to extend a protocol-adopting protocol as a way of injecting default implementations of that protocol's requirements into an ultimate adopting class? Yes, it is; this code is legal:
Okay, so what else does your code do? Well, it also injects an additional requirement (the instance variable). Is that legal? Yes, it is. This code is legal too:
So what is that Swift doesn't like? What haven't we done here, that your code does? It's the fact that the original protocol is
@objc
. If we changeprotocol Speaker
to@objc protocol Speaker
(and make all other necessary changes), the code stops compiling:I'm going to guess that this is because Objective-C knows nothing about protocol extensions. Since our implementation of the required protocol methods depends upon the protocol extension, we cannot adopt the protocol in a way that satisfies the compiler that the requirement has been satisfied from Objective-C's point of view. We have to implement the requirements right there in the class, where Objective-C can see our implementation (which is exactly what your solution does):
So, I conclude that your solution is as good as it gets.
What you're doing is actually more like this, where we use an extension on the adopter class to inject the "hook" methods:
That works because that last
extension
is something Objective-C can see: an extension on an Objective-C class is effectively a category, which Objective-C understands.