我正在构建简单的主题引擎,并希望有一个将UISwipeGestureRecognizer
添加到UIViewController
这是我的代码:
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)
}
}
当然,编译器无法找到#selector(Self.switchCurrentTheme)
,因为它不是通过@objc
指令公开的。是否可以将此行为添加到我的扩展程序中?
更新: Theme
是一个Swift枚举,因此我无法在@objc
协议前添加Themeable
答案 0 :(得分:14)
我能想到的最干净,最有效的解决方案是使用相关方法在UIViewController
上定义私有扩展。通过将范围限制为private
,可以在定义协议的源文件中隔离对此方法的访问。以下是它的外观:
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)
}
}
答案 1 :(得分:0)
我找到了解决方案。可能不是完美的,但它的确有效。
我无法将Themeable
协议定义为@objc
,因为它仅使用Swift enum
我决定移动我想要调用的方法" parent"协议并将此协议定义为@objc
。它似乎有效,但说实话,我真的不喜欢它......
@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)
}
}
答案 2 :(得分:0)
您是否考虑过创建一个包装器,让您从@objc中调用非@objc函数?
@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)
}
}
答案 3 :(得分:0)
这是一个类似的用例,您可以通过选择器调用方法,而无需像通过使用dynamic关键字那样迅速使用@objc。这样,您将指示编译器隐式使用动态调度。
import UIKit
protocol Refreshable: class {
dynamic func refreshTableData()
var tableView: UITableView! {get set}
}
extension Refreshable where Self: UIViewController {
func addRefreshControl() {
tableView.insertSubview(refreshControl, at: 0)
}
var refreshControl: UIRefreshControl {
get {
let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
if let control = _refreshControl[tmpAddress] as? UIRefreshControl {
return control
} else {
let control = UIRefreshControl()
control.addTarget(self, action: Selector(("refreshTableData")), for: .valueChanged)
_refreshControl[tmpAddress] = control
return control
}
}
}
}
fileprivate var _refreshControl = [String: AnyObject]()
class ViewController: UIViewController: Refreshable {
@IBOutlet weak var tableView: UITableView! {
didSet {
addRefreshControl()
}
}
func refreshTableData() {
// Perform some stuff
}
}