1

I am trying to add default implementations to UIViewController touches began to all controllers conforming to a protocol through a protocol extension. There the touch would be sent to a custom view all controllers implementing this protocol have.

Here's the initial state:

protocol WithView {
    var insideView: UIView! { get }
}

class Controller1: UIViewController, WithView {

    var insideView: UIView!

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        insideView.touchesBegan(touches, with: event)
    }

    /* Functionality of Controller 1 */
}

class Controller2: UIViewController, WithView {

    var insideView: UIView!

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        insideView.touchesBegan(touches, with: event)
    }

    /* Functionality of Controller 2 */
}

What I'd like to accomplish is a situation where all the UIViewControllers forwarded the touches to the insideView without specifying so for every controller the same way. Something like this:

protocol WithView {
    var insideView: UIView! { get }
}

extension UIViewController where Self: WithView {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        insideView.touchesBegan(touches, with: event)
    }
}

class Controller1: UIViewController, WithView {

    var insideView: UIView!

    /* Functionality of Controller 1 */
}

class Controller2: UIViewController, WithView {

    var insideView: UIView!

    /* Functionality of Controller 2 */
}

But this does not compile, saying 'Trailing where clause for extension of non-generic type UIViewController'

I tried to define it the other way around, like so:

extension WithView where Self: UIViewController {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        insideView.touchesBegan(touches, with: event)
    }
}

and while the extension is properly formatted, the compiler complains, as it cannot 'override' things in a protocol extension.

What I'd like is a class extension constrained to a protocol, such as I can override this methods and not being forced to copy-paste code inside all my controllers implementing this protocol.

Edit: as per proposed solutions

I also came up with this solution:

protocol WithView {
    var insideView: UIView! { get }
}

extension UIViewController {
    override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let viewSelf = (self as? WithView) else {
            super.touchesBegan(touches, with: event)
            return
        }
        viewSelf.insideView.touchesBegan(touches, with: event)
    }
}

class Controller1: UIViewController, WithView {

    var insideView: UIView!

    /* Functionality of Controller 1 */
}

class Controller2: UIViewController, WithView {

    var insideView: UIView!

    /* Functionality of Controller 2 */
}

It does what I want, but it feels a bit messy though, because then all the UIViewControllers would intherit this behavior, and would override its code, checking if they implement the protocol.

Jordi Serra
  • 113
  • 4

2 Answers2

2

You can define your own superclass for all view controllers and check if self conforms to the particular protocol (WithView in your case) to decide if you should forward touch events to any other view.

class MyViewController: UIViewController {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let selfWithView = self as? WithView {
            selfWithView.insideView.touchesBegan(touches, with: event)
        } else {
            super.touchesBegan(touches, with: event)
        }
    }
}

This is more flexible approach, you don't have to store insideView property in every view controller subclass.

0xNSHuman
  • 638
  • 5
  • 8
  • I came up with a slightly better approach, so instead of this I would have an extension on UIViewController, overriding touchesBegan, that would do the same you're saying. But it still feels a messy though... I think there shoud be a better 'swifty' solution to this – Jordi Serra Mar 30 '17 at 08:18
  • @JordiSerra agreed. I switched to Swift from Obj-C only a few months ago to be honest, so exploring its benefits too. Nice way of using extension instead of subclassing though! – 0xNSHuman Mar 30 '17 at 08:33
  • 2
    I've just learned my approach is wrong LOL. [here][https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html] states that "extensions cannot override existing functionality". So it seems that, although the compiler compiles and runs nice, it is disallowed to do that, and in principle it could lead to undesired behaviors. – Jordi Serra Mar 30 '17 at 13:01
  • Aahh, makes sense – 0xNSHuman Mar 30 '17 at 13:03
1

You could do this by creating a class and sub-classing from it:

class WithViewController: UIViewController, WithView {

    var insideView: UIView!

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        insideView.touchesBegan(touches, with: event)
    }
}

class ViewController: WithViewController {

}

The only downside to this is you have to have a default insideView and it never get's changed.

Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
  • This works, but the point here was to change a subclassing pattern (in my example, Controller1 used to inherit from a 'WithViewController', having this view encapsulated inside a UIViewController). I wanted to avoid this, because it defeats the purpose, because I then would be reproducing the same pattern – Jordi Serra Mar 30 '17 at 08:13