Jordi Serra
Jordi Serra

Reputation: 113

Overriding methods in a class extension constrained to a protocol in swift

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.

Upvotes: 1

Views: 1366

Answers (2)

0xNSHuman
0xNSHuman

Reputation: 648

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.

Upvotes: 2

Caleb Kleveter
Caleb Kleveter

Reputation: 11484

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.

Upvotes: 1

Related Questions