Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling selector from protocol extension

I'm building simple theme engine and would like have an extension which adds UISwipeGestureRecognizer to UIViewController

Here is my code:

protocol Themeable {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

extension Themeable where Self: UIViewController {
    func switchCurrentTheme() {
        Theme.switchTheme()
        themeDidUpdate(Theme.currentTheme)
    }

    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(Self.switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}

Of course compiler can't find #selector(Self.switchCurrentTheme) as it isn't exposed via @objc directive. Is it possible to add this behaviour to my extension?

UPDATE: Theme is a Swift enum, so I can't add @objc in front of Themeable protocol

like image 372
OgreSwamp Avatar asked Apr 14 '16 16:04

OgreSwamp


2 Answers

The cleanest, working solution I could come up with was to define a private extension on UIViewController with the method in question. By limiting the scope to private, access to this method is isolated to within the source file where the protocol is defined in. Here's what it looks like:

protocol Themeable {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

fileprivate extension UIViewController {
    @objc func switchCurrentTheme() {
        guard let themeableSelf = self as? Themeable else {
            return
        }

        Theme.switchTheme()
        themeableSelf.themeDidUpdate(Theme.currentTheme)
    }
}

extension Themeable where Self: UIViewController {
    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}
like image 61
dwlz Avatar answered Nov 12 '22 18:11

dwlz


I found a solution. May be not the perfect one, but it works. As I can't define Themeable protocol as @objc because it uses Swift-only enum I decided to move method I want to call to "parent" protocol and define this protocol as @objc. It seems like it works but I don't really like it to be honest...

@objc protocol ThemeSwitcher {
    func switchCurrentTheme()
}

protocol Themeable: ThemeSwitcher {
    func themeDidUpdate(currentTheme: Theme) -> Void
}

extension Themeable where Self: UIViewController {
    func switchCurrentTheme() {
        Theme.switchTheme()
        themeDidUpdate(Theme.currentTheme)
    }

    func addSwitchThemeGestureRecognizer() {
        let gestureRecognizer = UISwipeGestureRecognizer(target: self, action:#selector(switchCurrentTheme))
        gestureRecognizer.direction = .Down
        gestureRecognizer.numberOfTouchesRequired = 2
        self.view.addGestureRecognizer(gestureRecognizer)
    }
}
like image 1
OgreSwamp Avatar answered Nov 12 '22 19:11

OgreSwamp