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
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)
}
}
Have you considered creating a wrapper to let you call your non-@objc function from an @objc one?
@objc class Wrapper: NSObject {
let themeable: Themeable
init(themeable: Themeable) {
self.themeable = themeable
}
func switchCurrentTheme() {
Theme.switchTheme()
themeable.themeDidUpdate(Theme.currentTheme)
}
}
protocol Themeable {
func themeDidUpdate(currentTheme: Theme) -> Void
}
extension Themeable where Self: UIViewController {
func addSwitchThemeGestureRecognizer() {
let wrapper = Wrapper(themeable: self)
let gestureRecognizer = UISwipeGestureRecognizer(target: wrapper, action:#selector(Wrapper.switchCurrentTheme))
gestureRecognizer.direction = .Down
gestureRecognizer.numberOfTouchesRequired = 2
self.view.addGestureRecognizer(gestureRecognizer)
}
}
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)
}
}
I'm not sure but you can access it like
#selector(switchCurrentTheme)
or you can refer this link to try
Visit How to use #selector(myMethodName) in a protocol extension?